Projektowanie i wytwarzanie aplikacji internetowych (część 8)

Sebastian Wyrwał

W poprzednim odcinku zaprezentowano działający szkielet
aplikacji do kształcenia zdalnego. Duży nacisk położono na zagadnienia związane
z realizacją interakcji pomiędzy klientem studenckim a serwerem, przy pomocy
architektury CORBA. Naszkicowano również możliwości obsługi zdarzeń w
systemie do zdalnego kształcenia. W tym odcinku nastąpi bardziej dokładna
prezentacja tego zagadnienia, jak również zostanie pokazane rozwiązanie służące
do przesyłania danych np. plików graficznych. Aby zmniejszyć ilość
prezentowanego kodu, również w tym odcinku pominięto importy przy prezentacji
poszczególnych przykładów – takie, jak:

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;
import java.util.*;

jak również przedstawiono wyłącznie nowe lub zmienione
fragmenty kodu źródłowego. Wymaga to od czytelnika pewnej uwagi, z drugiej
strony czyni przykład bardziej przejrzystym.

Jak to działa – czyli krótkie przypomnienie

Przed dalszą prezentacją przykładu warto dokonać pobieżnego
przeglądu zaprezentowanego rozwiązania, z pominięciem jednak szczegółów
dotyczących architektury CORBA, gdyż na nich właśnie skupiał się odcinek
poprzedni.

  • Serwer po uruchomieniu tworzy obiekt klasy StudLoginActionImplementation,
    który pozwala na rejestrację (przy pierwszym korzystaniu z systemu do
    zdalnej edukacji) i logowanie studenta. Obiekt ten jest rzutowany do
    interfejsu StudLoginAction i jest identyfikowany przez usługę nazw jako LoginService.

  • Klient tworzy obiekt klasy JPanelPrezentacji, który będzie
    interfejsem graficznym interakcji studenta z prowadzącym. Po naciśnięciu
    przez studenta przycisku logowanie, klient uzyskuje od serwera odnośnik
    do obiektu StudLoginAction (po stronie serwera obiekt klasy
    StudLoginActionImplementation), następnie tworzy obiekt klasy
    JpanelPrezentacjiCallBackImpl i rzutuje go do interfejsu
    JPanelPrezentacjiCallBack. Po wykonaniu tych operacji na rzecz odnośnika do
    zdalnego obiektu StudLoginActionImplementation, wywoływana jest metoda
    login, do której oprócz nazwy logowania i hasła przekazywana jest
    referencja do utworzonego przed chwilą po stronie klienta obiektu
    JPanelPrezentacjiCallBack. Wywołana metoda zwraca referencje do utworzonego
    po stronie serwera obiektu klasy JPPImpl (zrzutowaną do interfejsu JPP).

Po powyższych interakcjach klient posiada referencję do
znajdującego się po stronie serwera obiektu JPPImpl, który implementuje
interfejs JPP. Serwer natomiast posiada referencję do znajdującego się po
stronie klienta obiektu klasy JpanelPrezentacjiCallBackImpl, który implementuje
interfejs JPanelPrezentacjiCallBack. Przy pomocy pierwszego z wymienionych
interfejsów klient studencki komunikuje się z serwerem, przy pomocy drugiego
zachodzi komunikacja w kierunku odwrotnym.

Poniżej zaprezentowano te interfejsy (w języku Java), a mówiąc
bardziej precyzyjnie: interfejsy, z których one dziedziczą. Interfejs
JPanelPrezentacjiCallBack dziedziczy z interfejsu
JpanelPrezentacjiCallBackOperations, nie wprowadzając żadnych nowych operacji.

Tabela 1. Główne czynności wywoływane przez
studenckiego klienta oraz przez serwer

Klient Serwer
JPanelPrezentacji  
  StudLoginActionImplementation
JPanelPrezentacjiCallBackImpl  
  JPPImpl
  Jednostka

public interface JPPOperations
{
    void sendEvents (sJPPEvent[] jppevents) throws
JPPPackage.JPPException;
    void help (String arg) throws
JPPPackage.JPPException;
    int next () throws JPPPackage.JPPException;
    boolean logout () throws JPPPackage.JPPException;
    boolean begin () throws JPPPackage.JPPException;
    void askTeacher (String msg) throws JPPPackage.JPPException;
    int executeCommand (String command) throws
JPPPackage.JPPException;
    void askForRaport (String desc) throws
JPPPackage.JPPException;
    String getName () throws JPPPackage.JPPException;
} // interface JPPOperations

public interface JPP extends JPPOperations,
org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity
{
} // interface JPP

Interfejs do komunikacji studenckiego klienta z serwerem
wraz ze swoim interfejsem bazowym.

Operacja sendEvents służy do przesłania zdarzeń
generowanych przez kontrolki wstawione do panelu prezentacji oraz stanów tych
kontrolek. Operacja help służy do wywołania pomocy, next powoduje
przetworzenie następnej jednostki.

Operacja logout służy do wylogowania, begin do zapoczątkowania
interakcji, jeśli ma ją rozpocząć student. Przy pomocy operacji askTeacher
klient studencki może przesłać zapytanie bezpośrednio do prowadzącego,
operacja executeCommand umożliwia przesłanie i wykonanie po stronie serwera
pewnego polecenia, askForRaport umożliwia otrzymanie od prowadzącego raportu o
postępach w nauce, getName służy do pobrania nazwy jednostki. Wywołanie każdej
z operacji może spowodować rzucenie wyjątku JPPException.

public interface JPanelPrezentacjiCallBackOperations
{
    void insertString (String s) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void insertNewLine () throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void insertPicture (String name) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void insertTable (String dane) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void setStyle (String style) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void clear () throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void sendData (int rozmiar, byte[] d, String polecenie) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void insertComponent (String typ, String nazwa, String id, boolean
reakcja) throws JPanelPrezentacjiCallBackPackage.JPPCallBackException;
    void ShowMessageBox (String msg);
} // interface JPanelPrezentacjiCallBackOperations

 

public interface JPanelPrezentacjiCallBack extends
JPanelPrezentacjiCallBackOperations, org.omg.CORBA.Object,
org.omg.CORBA.portable.IDLEntity
{
} // interface JPanelPrezentacjiCallBack

Interfejs do manipulowania panelem prezentacji od strony serwera
wraz ze swym interfejsem bazowym.

Operacja insertString służy do wstawienia łańcucha znaków
do panelu prezentacji, insertNewLine służy do przejścia do nowej linii,
insertPicture umożliwia wstawienie do panelu prezentacji obrazka, insertTable –
tabelki. Style wyświetlanego tekstu można zmieniać przy pomocy operacji
setStyle, usuwać zawartość panelu prezentacji można zaś przy pomocy
operacji clear. Do przesyłania danych (np. obrazka, nowej klasy!) służy
metoda sendData. Przy przesyłaniu danych należy określić, co klient ma zrobić
z otrzymanymi danymi. Przesłanie np. polecenia SAVE oznacza, że dane mają być
zapisane na dysku. Kontrolki – takie jak przyciski, etykiety, pola edycyjne –
wstawiać można przy pomocy metody insertComponent. Do wyświetlenia okna
dialogowego z komunikatem służy operacja ShowMessageBox.

Przyglądając się powyższym zestawom operacji można dojść
do wniosku, iż są one nieco nadmiarowe lub zaprojektowano je w sposób
niekonsekwentny. Obok siebie istnieją zarówno operacje realizujące konkretną
czynność, jak i operacje bardzo uniwersalne, służące do wykonania
polecenia. Trzeba jednak pamiętać, że zinterpretowanie polecenia pociąga
pewien nakład czasowy – dlatego warto dla typowych, często wykonywanych
czynności mieć specjalizowane operacje. Z drugiej jednak strony, ze względu
na duże niedospecyfikowanie projektu (które wynika np. z tego, że nie wiadomo
do nauki jakiego przedmiotu zostanie użyta aplikacja) warto mieć bardzo
uniwersalne operacje. Będą one wykorzystywane w miarę potrzeby np. w
przypadku użycia systemu do nietypowych zastosowań.

Obsługa zdarzeń generowanych po stronie klienta

Przedmiotem dalszych rozważań będą tylko zdarzenia
generowane przez te komponenty, które zostały wstawione do panelu prezentacji
zdalnie – czyli w wyniku interakcji
z klasą Jednostka. Zdarzenie lub stan kontrolki reprezentowane jest przez
obiekt klasy sJPPEvent, atrybut source określa źródło zdarzenia,
czyli jednoznacznie identyfikuje kontrolkę, która wygenerowała zdarzenie lub
której stan jest przesyłany. Atrybut arg służy do przechowywania
danej związanej ze stanem lub zdarzeniem – może to być, np. zawartość
pola edycyjnego.

public final class sJPPEvent implements
org.omg.CORBA.portable.IDLEntity
{
    public String source = null;
    public String arg = null;
    public sJPPEvent (){
    }

    public sJPPEvent (String _source, String _arg){
            source = _source;
            arg = _arg;
    }
}

Wstawiając kontrolkę określa się, czy ma być ona aktywna
(tzn. interakcja z nią ma spowodować odesłanie zdarzenia do serwera) czy
pasywna (ma być wstawiona do pewnej kolejki, a jej stan ma być przesłany
dopiero przy interakcji z kontrolką aktywną). Przykładem może być formularz
składający się z kilku pól edycyjnych i przycisku zatwierdź. Dane
mają być przesłane dopiero po naciśnięciu przycisku zatwierdź. Tak
więc pola edycyjne będą kontrolkami pasywnymi, a przycisk zatwierdź będzie
kontrolką aktywną. Do wstawiania kontrolek do panelu prezentacji służy
metoda insert z interfejsu JpanelPrezentacjiCallBack. Ma ona następującą
sygnaturę (w języku Java):

void insertComponent(String typ, String nazwa, String id, boolean reakcja)

  • typ – jest nazwą typu kontrolki w Java Swing, aktualnie obsługiwane są
    następujące typy: JLabel, JButton, JCheckBox.
  • nazwa – jest nazwą konkretnej kontrolki. Nazwa ta zostanie zależnie
    od typu tej ostatniej wyświetlona obok niej lub użyta do jej zainicjowania.
  • id – jest unikalnym identyfikatorem kontrolki, jest on nadawany po
    stronie serwera. Przy przesyłaniu zdarzeń i stanów atrybut source obiektu
    klasy sJPPEvent, która opisuje zdarzenie przybiera jego wartość.
  • reakcja – określa, czy kontrolka ma być aktywna czy pasywna.

Do obsługi zdarzeń generowanych przez kontrolki i do ich
tworzenia wprowadzono zarządcę zdarzeń. Jego funkcję pełni obiekt klasy
EventManager. Zastosowanie zarządcy pociąga za sobą pewne zmiany w
prezentowanym ostatnio kodzie – nie wynikają one z niestarannego
projektowania, ale są skutkiem dodawania do aplikacji dodatkowej funkcjonalności.
Zarządca używany jest przy wstawianiu kontrolek do panelu prezentacji. Jego
rola polega wtedy na rozpoznaniu typu kontrolki w postaci łańcucha tekstu
i utworzeniu kontrolki odpowiedniej klasy. Dodatkowo nowo-tworzona kontrolka jeśli
jest pasywna, wstawiana jest do kolejki (wektora), jeśli jest aktywna – zarządca
rejestruje się jako obiekt nasłuchujący generowanych przez nią zdarzeń. W
przypadku interakcji z kontrolką aktywną zostaje wywołana odpowiednia metoda
zarządcy np. actionPerformed. Spowoduje to stworzenie ciągu obiektów, z których
pierwszy będzie opisywał zdarzenie, które właśnie zaszło a następne będą
opisywać stany kontrolek z kolejki.

Prezentowana klasa ma dwa atrybuty, pierwszy z nich – _jpp – jest uchwytem do interfejsu JPP, który służy do interakcji studenckiego
klienta z serwerem; drugi – _vect – jest wektorem (kolejką), do którego
wstawiane są kontrolki pasywne (innymi słowy są to te kontrolki, dla których
argument reakcja przy ich wstawianiu miał wartość false). Metoda
setJPP służy do ustawienia uchwytu do interfejsu JPP, który został uzyskany
podczas logowania do systemu. Kluczowe znaczenie dla działania prezentowanej
klasy mają metody insertComponent, stateToString i actionPerformed. Pierwsza z
nich, jak już powiedziano, na podstawie łańcucha z nazwą typu komponentu
tworzy ów komponent. Jeśli argument reakcja ma wartość true to zarządca
zdarzeń (EventManger) jest rejestrowany w kontrolce (kontrolka staje się
„aktywna”) jako obiekt nasłuchujący na zdarzenia, w przeciwnym wypadku
komponent jest wstawiany do wektora. Przy okazji następuje zainicjowanie
komponentu przy pomocy łańcucha nazwa.
Z każdą kontrolką skojarzono pewne – identyfikowane przez klucze – atrybuty użytkownika. Do przypisania kontrolce atrybutów użytkownika służy
metoda putClientProperty(klucz,wartość) do ich pobrania
getClientProperty(klucz). Dla wstawianych kontrolek zdefiniowano klucze
SENDEVENT, KEY, NAME. Pierwszy ma wartość true jeżeli kontrolka jest aktywna,
false w przypadku przeciwnym; drugi jest identyfikatorem kontrolki, jaki nadano
jej po stronie serwera, trzeci odpowiada nazwie kontrolki. Warto dodać, iż
metody służące do ustawiania i odczytu atrybutów użytkownika korzystają z
wbudowanych słowników. Udogodnienia te są zdefiniowane w klasie JComponent.
Kolejna metoda zwraca stan komponentu w postaci tekstu. Dla pola edycyjnego jest
to tekst, który do niego wprowadzono. Dla kontrolki JCheckBox jest to wartość
true lub false – zależnie od tego, czy zostało ono zaznaczone czy nie. Do
rozpoznania obiektu służy operator instanceof, który zwraca wartość true w
wypadku, kiedy obiekt jest danej klasy. Ostania z metod jest wywoływana wtedy,
kiedy nastąpi interakcja z „aktywną” kontrolką. W pętli dla każdej
kontrolki z wektora _vect wywoływana jest omówiona wcześniej funkcja
stateToString, a następnie generowany jest obiekt klasy sJPPEvent opisujący
stan kontrolki. Polu source przypisuje się klucz (KEY) skojarzony
z kontrolką, polu arg jej stan przetłumaczony na łańcuch znaków przy
pomocy omówionej wcześniej metody stateToString. Tak wygenerowane obiekty
opisujące stany są wstawiane do wektora, z tym, że jako pierwszy wstawiany
jest stanu obiektu, który wygenerował zdarzenie. Następnie tworzona jest
tablica o wielkości równej długości wektora, do której przepisywane są
wstawione przed chwilą obiekty. Owa tablica jest jedynym argumentem metody
sendEvents, która przesyła stany (zdarzenia) do serwera.

public class EventManager implements ActionListener {
    JPP _jpp = null;
    Vector _vect = new Vector();
public EventManager(){
}

public void setJPP(JPP jpp){
    _jpp = jpp;
}

//tu rozpoznaje się typy komponentów i je tworzy
public JComponent insertComponent (String typ,
                                       
String nazwa,
                                       
String id,
                                       
boolean reakcja){
JComponent comp = null;
if (typ.equals("JButton")){
    JButton jb = new JButton(nazwa);
    if (reakcja == true)
           
jb.addActionListener(this);
    else
            _vect.add(jb);
            comp = jb;
    }
else if (typ.equals("JTextField")){
            comp = new
JTextField(nazwa);
            _vect.add(comp);
    }else if (typ.equals("JLabel")){
            comp = new
JLabel(nazwa);
    }else if (typ.equals("JCheckBox")){
            JCheckBox jcb = new
JCheckBox(nazwa,false);
            if (reakcja ==
true)
                   
jcb.addActionListener(this);
            else
                   
_vect.add(jcb);
            comp = jcb;
    }

    if (reakcja ==true)
               
comp.putClientProperty("SENDEVENT","true");
    else
               
comp.putClientProperty("SENDEVENT","false");
    comp.putClientProperty("KEY",id);
    comp.putClientProperty("NAME",nazwa);
    return comp;
}

private String stateToString(JComponent jc){
    if (jc instanceof JButton){
               
return new String("Przycisk");
    }else if (jc instanceof JTextField){
               
return ((JTextField)jc).getText();
    }else if (jc instanceof JCheckBox){
               
if (((JCheckBox)jc).isSelected()==true)
                           
return new String("true");
               
else
                           
return new String("false");
    }
return null;
}

public void actionPerformed(ActionEvent e){

    Vector events = new Vector();
    sJPPEvent ev = new sJPPEvent();
    JComponent sComp = (JComponent)e.getSource();
    ev.source = (String)sComp.getClientProperty("KEY");
    ev.arg = stateToString(sComp);
    events.add(ev);

    JComponent vComp = null;
    String arg = null;
    String key = null;

    for (int i = 0;i<_vect.size();i++){
            vComp = (JComponent)_vect.elementAt(i);
            arg =
stateToString(vComp);
            key = (String)vComp.getClientProperty("KEY");
            ev = new
sJPPEvent();
            ev.source =
key;
            ev.arg = arg;
            events.add(ev);
    }
    sJPPEvent evtTable[] = new sJPPEvent[events.size()];
    for (int j = 0;j<events.size();j++)
            evtTable[j] =
(sJPPEvent)events.elementAt(j);
    try{
    _jpp.sendEvents(evtTable);
    }catch(JPPPackage.JPPException jppEx){
            System.out.println("przesyłanie zdarzeń nie powiodło się");
           
jppEx.printStackTrace();
            }
    }

}

Wykorzystanie zarządcy zdarzeń

Poniżej zaprezentowano te fragmenty kodu z poprzedniego
odcinka, które uległy zmianom w wyniku rozbudowy szkieletu aplikacji. W
metodzie actionPerformed klasy StudFrame tworzy się zarządcę zdarzeń (obiekt
klasy EventManger), a następnie przekazuje się go (mówiąc ściśle: jego
referencję) do konstruktora klasy JPanelPrezentacjiCallBackImpl. Ten ostatni
uległ zmianie w stosunku do wersji prezentowanej w poprzednim odcinku. Jest to
dopuszczalne, gdyż jest on używany wyłącznie lokalnie po stronie serwera.
Zmiana polega na dodaniu do niego argumentu będącego referencją właśnie do
zarządcy zdarzeń. Na końcu metody actionPerfomed zarządcy przekazuje się – uzyskaną w wyniku logowania
– referencję do interfejsu JPP, który umożliwia
zwrotną komunikację
z serwerem.

public void actionPerformed(ActionEvent evt){
try{
if (evt.getSource().equals(loginButton)){

System.out.println("logowanie");
String name = "LoginService";
//utworzono nowego zarządcę zdarzeń
EventManager _eventManager = new EventManager();
StudLoginAction loginAction =
StudLoginActionHelper.narrow(ncRef.resolve_str(name));
JPanelPrezentacjiCallBackImpl jPPCBI =
new JPanelPrezentacjiCallBackImpl(orb,rootpoa,
jPanelPrezentacji,_eventManager);
org.omg.CORBA.Object ref = rootpoa.servant_to_reference(jPPCBI);
JPanelPrezentacjiCallBack href = JPanelPrezentacjiCallBackHelper.narrow(ref);
jpp = loginAction.login ("12actr3", "12f3c3d3", href);
_eventManager.setJPP(jpp);


}
else if(evt.getSource().equals(logoutButton)){
    jpp.logout();
    }
else if(evt.getSource().equals(helpButton)){
    jpp.help("anything");
    }
else if (evt.getSource().equals(askTeacherButton)){
    jpp.askTeacher("Kiedy ma Pan konsultacje?");
    }

else if (evt.getSource().equals(nextButton)){
jpp.next();
}
}catch(Exception e1){
System.out.println("błąd przy przesyłaniu");
e1.printStackTrace();
}
}

Metoda actionPerformed klasy studFrame

Większe zmiany dotyczą klasy JPanelPreznetacjiCallBackImpl.
Dodano w niej atrybut, który jest referencją do zarządcy zdarzeń. Jak już
wspomniano, inicjuje się go w konstruktorze omawianej klasy. W metodzie
insertNewLine umieszczono wywołanie metody doNewLine panelu prezentacji. Do tej
ostatniej klasy oddelegowano również wstawianie plików graficznych.
Zaimplementowano także metodę sendData, która zostanie omówiona później.
Uproszczeniu uległa metoda insertComponent, ponieważ utworzenie komponentu
oddelegowano do zarządcy zdarzeń.

// nowy atrybut - referencja do zarządcy zdarzeń
EventManager _eventManager = null;
//zmienił się konstruktor
public JPanelPrezentacjiCallBackImpl(ORB orb, POA poa, JPanelPrezentacji jpp,
EventManager eventManager){
    _orb = orb;
    _poa = poa;
    _jpp = jpp;
    _eventManager = eventManager;
}
public void insertNewLine () throws
    JPanelPrezentacjiCallBackPackage.JPPCallBackException{
   

try{

    _jpp.doNewLine("",1);
    }catch(Exception e1){
            throw new
    JPanelPrezentacjiCallBackPackage.JPPCallBackException("Błąd przy
wstawianiu tekstu");
            }
    }
public void insertPicture (String name) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException{
    try{
    _jpp.doPicture(name,1);
    }catch(Exception e1){
        System.out.println("Nie można wstawić obrazka");
        e1.printStackTrace();
        }
        }

//rozpoznawanie typu komponentu i jego tworzenie oddelegowano do zarządcy
//zdarzeń
public void insertComponent (String typ, String nazwa, String id, boolean
reakcja) throws JPanelPrezentacjiCallBackPackage.JPPCallBackException{

   

JComponent component = _eventManager.insertComponent (typ,nazwa,id,reakcja);

    try{
        _jpp.insertComponent(component);

    }catch(Exception e1){
            throw new
JPanelPrezentacjiCallBackPackage.JPPCallBackException("Błąd przy
wstawianiu tekstu");
}
}

JPanelPrezentacjiCallBackImpl – zmiany

W klasie JPanelPrezentacji należy zmienić specyfikatory
zasięgu metod doNewLine i doPicture na publiczne, ponadto zaimplementowano ciało
tej ostatniej. Obrazek wstawiany jest jako etykieta, która została z kolei
zainicjowana obiektem klasy ImageIcon.

public int doPicture(String s, int i )throws BadLocationException{
    JLabel l = new JLabel(new ImageIcon(s));
    insertComponent(l);
    return i;
}

JPanelPrezentacji – zmiany

W klasie JPPImpl dodano nowy atrybut oraz zaimplementowano
ciało metody sendEvents:

Jednostka _jednostka = null;


public void sendEvents (sJPPEvent[] jppevents) throws
JPPPackage.JPPException{
   

if (_jednostka !=null)

        _jednostka.serviceEvents(jppevents);

    }

W klasie tej nieznacznie zmieniono również metodę next:

public int next () throws JPPPackage.JPPException{
    System.out.println("Następna jednostka");
    try{
    _jednostka = new Jednostka();
    _jednostka.JPP_Studentow.add(this);
    _jednostka.run();
    }catch(Exception ee){
    throw new JPPPackage.JPPException("Błąd nast. jednostki");
    }
    return 1;
    }

JPPImpl – zmiany

Przesyłanie danych

Do przesyłania danych służy metoda sendData interfejsu
JPanelPrezentacjiCallBack. Argument rozmiar określa ilość bajtów
danych, czyli jest równy rozmiarowi tablicy d,
w której przekazuje się dane do przesłania. Łańcuch polecenie określa,
co należy zrobić z danymi po stronie klienta. Obecnie jedyne rozpoznawane
polecenie to „SAVE nazwa”. Jego wykonanie polega na zapisaniu danych do
pliku nazwa. W toku dalszej implementacji możliwe jest oczywiście
zaimplementowanie innych poleceń. Do zapisu pliku służy obiekt klasy
FileOutputStream, parametrem jego konstruktora jest właśnie nazwa pliku.
Zapisanie danych realizuje się przy pomocy metody write, po zakończeniu tej
czynności należy strumień zamknąć – służy do tego metoda close.

public void sendData (int rozmiar, byte[] d, String polecenie) throws
JPanelPrezentacjiCallBackPackage.JPPCallBackException{
   

if (polecenie.startsWith("SAVE")){

        try{
        String nazwa = polecenie.substring(5);
        System.out.println("nazwa:"+nazwa+":");
        FileOutputStream out = new
FileOutputStream(nazwa);
        out.write(d);
        out.close();
    }catch(Exception e){
        System.out.println("problem przy zapisywaniu do pliku");
        e.printStackTrace();
    }
    }

}

JpanelPrezentacjiCallBackImpl – sendData

Testowanie rozwiązania

W klasie jednostka przedefiniowano metodę run
oraz dodano nową metodę służącą do obsługi zdarzeń. Tak zmieniona jednostka
wstawia po stronie klienta kilka kontrolek oraz obrazek, co pokazano na rysunku
1. Na rysunku 2 przedstawiono z kolei zdarzenia odebrane przez serwer. Opisują
one źródło zdarzenia (w tym wypadku interakcję
z przyciskiem zatwierdź) oraz stany pozostałych kontrolek. Ciągi ok1,
o1, o3, tf1 są identyfikatorami kontrolek. Obok nich znajdują się ich stany.
Dla pola tekstowego tf1 jest to ciąg, który został wprowadzony do tego pola
po stronie klienta. Metodę run jednostki, która powoduje taką
interakcję zaprezentowano poniżej:

public void run(){
    try{
    for (int i = 0;i<JPP_Studentow.size();i++){
        JPPImpl jpp_stud = (JPPImpl)JPP_Studentow.elementAt(i);
        jpp_stud._jppcb.insertString("Funkcja main służy do: n");
        jpp_stud._jppcb.insertNewLine();
        jpp_stud._jppcb.insertComponent("JCheckBox","Od niej, rozpoczyna się
wykonywanie programu w C.","o1",false);
        jpp_stud._jppcb.insertNewLine();
        jpp_stud._jppcb.insertComponent("JCheckBox","kończy wykonanie programu
w C.","o3",false);
        jpp_stud._jppcb.insertNewLine();
        jpp_stud._jppcb.insertComponent("JLabel","służy do","l1",false);
        jpp_stud._jppcb.insertComponent("JTextField","","tf1",false);
        jpp_stud._jppcb.insertNewLine();
        jpp_stud._jppcb.insertComponent ("JButton", "Zatwierdź", "ok1",
true);
        jpp_stud._jppcb.insertNewLine();

    FileInputStream file = new FileInputStream("test.jpg");
    byte tab[] = new byte[file.available()];
    try{
    file.read(tab);
    file.close();
    }catch (Exception e){
        System.out.println("Czytanie z pliku nie powiodło się");
        e.printStackTrace();
    }
    jpp_stud._jppcb.sendData(tab.length,tab,"SAVE test2.jpg");
    jpp_stud._jppcb.insertPicture("test2.jpg");
    }}catch(Exception ee){
        System.out.println("Błąd przy przetwarzaniu jednostki");
        }
    }

Jednostka – metoda run

public void serviceEvents(sJPPEvent[] jppevents){
    System.out.println("odebrano następujące zdarzenia n");
        for (int i = 0;i<jppevents.length;i++){
           
System.out.println(jppevents[i].source+" "+jppevents[i].arg+"n");

    }

}

Jednostka – nowa metoda serviceEvents



Rys. 1. Klient studencki po realizacji przykładowej jednostki


Rys. 2. Serwer po odesłaniu zdarzeń – zdarzenie i stany kontrolek

Podsumowując zaprezentowane rozwiązania można zauważyć,
że przy pomocy bardzo prostych rozwiązań charakterystycznych dla aplikacji
scentralizowanych i dwóch (trzech – jeśli liczyć interfejs służący do
logowania) niezbyt skomplikowanych interfejsów, można uzyskać dość
rozbudowaną funkcjonalność. Wstawianie i obsługa zdarzeń kontrolek jest
realizowana przez jedną klasę – zarządcę zdarzeń. Ma to dość poważne
konsekwencje, ponieważ nic nie stoi na przeszkodzie, aby klasę owego zarządcy
przesłać przy użyciu operacji służących do przesyłania danych. Umożliwia
to zastosowanie i obsługę nowych kontrolek. Gdyby chcieć podsumować rozwiązania
zastosowane w prezentowanym projekcie, to można zauważyć pewne
charakterystyczne sposoby interakcji obiektów
w aplikacjach edukacyjnych. Oczywiście, możliwe jest zastosowanie nieco innych
rozwiązań. Z zaprezentowanego przykładu wynika jednak, iż język Java wraz z
architekturą CORBA doskonale nadają się do tej klasy zastosowań.
Zaprezentowany przykład może być, zdaniem autora, dobrym punktem wyjścia
przy projektowaniu dużego systemu do kształcenia zdalnego.

Literatura

[1] OMG IDL Syntax and Semantics, December 2001 www.omg.com
[2] OMG IDL to Java Language Mapping www.omg.com
[3] CORBA, Passing References to Remote Objects – Callback Methods home.att.net/~baldwin.rick/Advanced/Java636.html
[4] E. Gamma Design Patterns Addison-Wesley 1998
[5] materiały na www.sun.com