12. Wprowadzenie do technologii CORBA


Wykład jest poświęcony programowaniu rozproszonemu według standardu CORBA. Po pobieżnym przedstawieniu tej technologii zobaczymy na praktycznym przykładzie jeden z możliwych sposobów zastosowania jej w środowisku Javy.

1. Wstęp

CORBA (Common Object Request Broker Architecture) jest przemysłowym standardem zaproponowanym przez OMG (Object Management Group) w 1991 roku. Jest to konsorcjum ponad 800 firm i instytucji zajmujące się m.in. pracami nad zdefiniowaniem standardowego, niezależnego od platformy systemowej i języka szkieletu rozproszonej komunikacji pomiędzy obiektami. CORBA jest przeznaczona do wspomagania programowania rozproszonego pomiędzy niekompatybilnymi systemami. Nie jest to kolejny język programowania ani też jego właściwość czy narzędzie stworzone w konkretnym języku. Jest to specyfikacja określająca technologię integracji składników programów rozproszonych. Określa standardowe metody dostępu do zdalnych obiektów i komunikacji pomiędzy nimi, niezależnie od systemu operacyjnego, języka programowania oraz położenia w sieci.

System oparty na CORBA jest zestawem obiektów, które oddzielają dostawców usług (serwery) od odbiorców (klienci) przy pomocy dobrze określonego interfejsu programistycznego. Ich zadaniem jest świadczenie różnego rodzaju usług, z których najważniejsze to lokalizowanie obiektu-serwera po nazwie i zapewnienie komunikacji pomiędzy nim a klientem, w szczególności przekazywanie argumentów i rezultatów metod. Funkcję tę spełnia specjalny program-serwer dostarczany wraz z implementacją środowiska CORBA. Musi on być uruchomiony przed rozpoczęciem działania pozostałych składników rozproszonego programu, podobnie jak rejestr RMI.

Implementacja środowiska CORBA, jak również oprogramowanie obiektów świadczących usługi (serwerów) i z nich korzystających (klientów) mogą być wykonane w różnych językach programowania. Aby ich współdziałanie było możliwe, muszą one spełniać specyfikację, która jest definiowana przed ich implementacją. Do tworzenia specyfikacji obiektów służy specjalny język. Z napisanej w nim specyfikacji generuje się kod w języku docelowym wspomagający implementację obiektów. Wygenerowany kod umożliwia odwoływanie się do obiektów zdalnych przy użyciu zwykłych mechanizmów języka implementacji obiektu lokalnego. Jest to tym ciekawsze, że owe obiekty mogą być zaimplementowane w różnych językach programowania.

Wielu różnych dostawców oprogramowania udostępnia swoje implementacje standardu CORBA. Są one między sobą mniej lub bardziej zgodne, ze względu na różnice w kolejnych wersjach specyfikacji. Różnice te objawiają się głównie po stronie serwera, mniej po stronie klienta. Jedna z nich jest dostarczana wraz SDK Javy (od wersji 1.3).


2. Podstawy technologii CORBA

Budowa i zasady działania CORBA są bardzo złożone, dlatego ograniczymy się tu do pobieżnego przedstawienia wyłącznie elementów niezbędnych do zbudowania najprostszych aplikacji.

2.1. Architektura

OMA (Object Management Architecture - Architektura Zarządzania Obiektami) to specyfikacja zaproponowana przez OMG, definiująca warunki, jakie powinny spełniać rozproszone aplikacje obiektowe. Na jej podstawie został zdefiniowany standard CORBA, w którym można wyróżnić 5 zasadniczych elementów:

ORB - Object Request Broker (Pośrednik Zleceń Obiektowych)

Za komunikację pomiędzy pośrednikami ORB jest odpowiedzialny protokół GIOP - General Inter-ORB Protocol. Określa on wspólną reprezentację danych, formaty komunikatów i założenia, co do sieciowej warstwy transportowej, która ma być użyta do przekazywania komunikatów. Obecnie, ze względu na dominację sieci opartych na TCP/IP, stworzono specyfikację warstwy transportu dla GIOP o nazwie IIOP (Internet Inter-ORB Protocol). Protokół ten jest aktualnie używany w komunikacji pomiędzy pośrednikami ORB w Internecie.

ORB jest zasadniczą częścią implementacji standardu CORBA. Jest to oprogramowanie, którego zadaniem jest zapewnienie komunikacji pomiędzy obiektami w sieci. W szczególności pozwala na zlokalizowanie zdalnego obiektu i uzyskanie odniesienia do niego w programie. Oprócz tego jest odpowiedzialne za przesyłanie argumentów i rezultatów metod pomiędzy wołającym i adresatem komunikatu. Może kierować żądania do innego pośrednika ORB, jeśli zarządza on daną usługą. CORBA definiuje ogólne zasady komunikacji pomiędzy pośrednikami ORB w sposób niezależny od ich implementacji jak również użytego języka programowania czy systemu operacyjnego, ponieważ protokoły są zdefiniowane przy pomocy IDL.





Klient wysyła żądanie do serwera za pośrednictwem ORB.

IDL - Interface Data Language (Język Definicji Interfesju)

Odwzorowanie musi zapewnić możliwość wyrażenia konstrukcji składniowych (deklaracji) języka IDL w języku docelowym, przy zachowaniu ich semantyki. W szczególności chodzi o odzwierciedlenie podstawowych i złożonych typów danych, odwołań do obiektów, wywołań metod wraz z przekazywaniem argumentów i wyników itd.
Dobre, znormalizowane odwzorowanie gwarantuje, że ten sam kod może być stosowany w dowolnym pośredniku ORB.

Zarówno klient, jak i obiekt, do którego się on odwołuje są oddzieleni od ORB warstwą interfejsów języka IDL. Podobnie jak w przypadku zdalnych obiektów RMI, obiekty CORBA muszą być specyfikowane przy pomocy interfejsów. Ponieważ jednak w CORBA obiekty mogą być implementowane w różnych językach, do tworzenia interfejsów stworzono specjalny pośredni język IDL. Jego składnia jest podobna do Javy czy C++, jednak nie jest to zwykły język programowania. Jego przeznaczeniem jest wyłącznie specyfikowanie interfejsów, z których następnie generuje się kod wspomagający (namiastki, szkielety itp.) w języku implementacji. Do wygenerowania tego kodu potrzebny jest specjalny kompilator. Oczywiście musi istnieć odwzorowanie języka definicji interfejsów IDL na język, w którym implementujemy obiekt. Do tej pory OMG zdefiniowało odwzorowania IDL na następujące języki programowania: Ada, C, C++, COBOL, Common Lisp, Java i Smalltalk. Różne organizacje wprowadzają mniej formalne odwzorowania na inne znane języki (Perl, Python, TCL, Eiffel, Haskell, Erlang). Historycznie pierwsze pojawiły się odwzorowania dla języków C i Smalltalk, obecnie najpowszechniejsze są odwzorowania dla Javy i C++.

DII - Dynamic Invocation Interface (Interfejs Dynamicznych Wywołań)

Interfejsy obiektów mogą być statyczne (znane w czasie kompilacji) lub dynamiczne. Interfejsy statyczne są reprezentowane po stronie klienta przez namiastki (wygenerowane klasy). Interfejsy dynamiczne pozwalają klientom używać obiektów CORBA, których typy nie były znane w czasie kompilacji. Do tego właśnie służy DII.

IR - Interface Repository (Repozytorium Interfejsów)

Do pozyskania dynamicznych interfejsów służy ich przechowalnia - repozytorium interfejsów IR. Interfejsy mogą być dodawane do repozytorium na użytek klientów. Mogą oni w ten sposób uzyskiwać dostęp do obiektów nieznanych w czasie kompilacji (klienta). Na podstawie interfejsu pozyskanego z repozytorium klient może zbudować i wysłać żądanie do obiektu, który ten interfejs implementuje.

OA - Object Adapters (Adaptery Obiektów)

Adapter obiektu pośredniczy pomiędzy ORB a implementacją obiektu w dostępie do usług, jakie ten świadczy. Chodzi tu np. o generowanie referencji do obiektów, wywoływanie metod i kwestie bezpieczeństwa z tym związane.


POA pośredniczy w dostępie do obiektu serwera

Początkowo był zdefiniowany podstawowy adapter obiektu BOA (Basic Object Adapter). Jednak jego specyfikacja nie była wystarczająco dokładnie określona, co powodowało problemy z przenośnością oprogramowania pomiędzy pośrednikami ORB różnych producentów. Zaowocowało to powstaniem przenośnego adaptera obiektów POA (Portable Object Adapter). W implementacji CORBA zawartej w J2SDK wersji 1.3 używany był jeszcze BOA, podczas gdy w wersji 1.4 dostępne jest już POA. Na tej ostatniej oparty będzie przykład zamieszczony w dalszej części.

2.2. Usługi składowe - COS

Oprócz specyfikacji CORBA, wspomniana architektura zarządzania obiektami OMA definiuje zestaw usług składowych COS (Common Object Services). Mają one ułatwiać stosowanie CORBA dzięki użyciu standardowych interfejsów (napisanych w języku IDL) do ich specyfikacji. Najważniejszą z tych usług jest Object Naming Service, która spełnia zadanie podobne do rejestru RMI: pozwala na kojarzenie obiektów z ich nazwami. Znając nazwę można uzyskać referencję do obiektu. Niektóre z pozostałych usług to:

2.3. Schemat postępowania

Tworzenie typowej rozproszonej aplikacji w standardzie CORBA przeważnie wymaga podjęcia następujących kroków:

  1. Zdefiniowanie specyfikacji w języku IDL
  2. Kompilacja specyfikacji IDL do języka docelowego
  3. Implementacja serwera w oparciu o kod wygenerowany ze specyfikacji
  4. Zaprogramowanie klienta
  5. Uruchomienie pośrednika ORB, serwera i klienta

Poszczególne kroki mogą się nieco różnić w zależności od producenta implementacji środowiska CORBA i jego wersji, jednak podstawowe zasady pozostają podobne.

serwer

Zaprogramowanie serwera obejmuje implementację metod określonych w interfejsie IDL oraz utworzenie kodu inicjującego system CORBA i instalującego usługę. Przeważnie czynności te są oddelegowane do metody main() dodatkowej klasy. Jeśli używamy adaptera POA (jak w J2SDK 1.4), to czynności te wyglądają następująco:

Ten schemat jest właściwy dla serwerów nietrwałych (transient).
  1. Utworzenie i inicjalizacja ORB
  2. Pobranie referencji do korzenia POA i aktywacja zarządcy POAManager
  3. Utworzenie obiektu świadczącego usługę i pobranie referencji do niego
  4. Pobranie referencji do usługi nazywania
  5. Zarejestrowanie obiektu serwera pod nową nazwą (korzystając z pobranych referencji)
  6. Oczekiwanie na zgłoszenia od klientów

klient

Program klienta musi uzyskać referencję do obiektu serwera. Podobnie jak w przypadku RMI obiekt serwera będzie reprezentowany lokalnie przez namiastkę. Referencję do serwera wydobywa się z usługi nazywania poprzez nazwę. W związku z tym trzeba najpierw uzyskać dostęp do usługi nazywania - podobnie jak po stronie serwera. To z kolei wymaga inicjalizacji pośrednika ORB. Krótko mówiąc klient musi wykonać następujące standardowe czynności:

  1. Utworzenie i inicjalizacja ORB
  2. Pobranie referencji do usługi nazywania
  3. Pobranie referencji do obiektu serwera za pośrednictwem nazwy
  4. Wywoływanie metod obiektu serwera

Po uzyskaniu referencji do obiektu serwera można wywoływać na jego rzecz metody zadeklarowane w interfejsie wygenerowanym ze specyfikacji, który on implementuje.

2.4. Tworzenie specyfikacji IDL

Język IDL służy wyłącznie do tworzenia specyfikacji, nie ma w nim więc konstrukcji typowych dla zwykłych języków programowania: instrukcji, operatorów, itp. Jego składnia umożliwia deklaracje metod i wprowadzanie nowych typów danych.

Wielkość liter nie ma znaczenia w identyfikatorach w tym sensie, że nazwy różniące się wyłącznie wielkością liter są utożsamiane. Kompilator generuje przy tym błąd. CORBA zakłada, że języki programowania, na które odwzorowuje się IDL nie rozróżniają wielkości liter.

składniki

Strukturą najwyższego poziomu są moduły wprowadzane słowem kluczowym module:

module Example {
    // interfejsy i typy danych
};
Moduły są tłumaczone na pakiety Javy. Służą do grupowania interfejsów w obrębie jednej przestrzeni nazw.

Interfejsy służą do grupowania powiązanych ze sobą operacji (funkcji, metod) i atrybutów. Są wprowadzane słowem kluczowym interface:

module Example {
    interface Operations {
        // deklaracje metod i atrybutów
    };
};
Interfejsy IDL są tłumaczone na interfejsy Javy.

typy danych

W języku IDL podstawowe typy danych są podobne jak w Javie czy C++. Można też tworzyć własne, złożone typy. Poniżej omówione zostaną ważniejsze z nich.

typy pierwotne
Typy pierwotne pokazuje tabelka:

Typ IDLOdpowiednik JavyOpis
short, unsigned short short 16 bitowa liczba całkowita
long, unsigned long int 32 bitowa liczba całkowita
long long, unsigned long long long 64 bitowa liczba całkowita
float float 32 bitowa liczba zmiennoprzecinkowa
double double 64 bitowa liczba zmiennoprzecinkowa
char, wchar char znak 8 lub 16 bitowy
boolean boolean typ logiczny o wartościach TRUE lub FALSE
octet byte typ znakowy niezmienny podczas przekazu

Oprócz tego występuje typ any mogący reprezentować dowolny typ pierwotny lub złożony. Typ octet nie podlega żadnym konwersjom podczas przekazywania pomiędzy różnymi systemami (np. kodowanie ASCII na EBCDIC).

typy szablonowe
Są to typy określające dane o zmiennym rozmiarze.

Słowo kluczowe sequence<typ> deklaruje wektor elementów typu typ o nieokreślonej długości. Rozmiar można ustalić podając go po przecinku:

sequence<string> message;
sequence<char, 80> line;
Odpowiednikiem typu sequence w Javie jest tablica.

Typ string oznacza łańcuch znakowy składający się ze znaków typu char zakończonych znakiem NULL. Łańcuch znakowy typu wstring jest złożony z elementów typu wchar.

typy złożone

Złożone są z typów podstawowych lub złożonych. Definiuje się je słowem kluczowym typedef lub struct.

definicja typedef
Słowem kluczowym typedef można wprowadzić nową nazwę na typ, podobnie jak w językach C/C++.
typedef string słowo;
typedef sequence<string> zdanie;
struktury
Słowem kluczowym struct definiuje się struktury służące do grupowania atrybutów:
struct Osoba {
    string imie;
    int    wiek;
};
Struktury są tłumaczone na klasy w Javie.

Wyjątki

Wyjątki można deklarować na poziomie modułów lub interfejsów słowem kluczowym exception:

exception Wyjątek {};
Są one tłumaczone na klasy wyjątków w Javie.

Operacje

Operacje IDL odpowiadają metodom Javy. Deklaracje operacji mają składnię podobną do metod Javy:

typ nazwa(listaParametrów);
typ nazwa(listaParametrów) raises(NazwaWyjątku);
typ jest typem IDL lub słowem kluczowym void. Jeśli metoda zgłasza wyjątek, to ma on być uwzględniony w klauzuli raises (której odpowiednikiem w Javie jest throws). Każdy parametr na liście parametrów jest poprzedzony jednym ze słów kluczowych: Wyjaśnienie tych sposobów przekazywania argumentów zobaczymy w zaprezentowanym dalej programie. Oto kilka przykładów:
string date();
void set(in int arg) raises(BadValue);
void get(out IntHolder val) raises(BadPlace, BadValue);
boolean check(inout StringHolder sval);
Druga metoda zgłasza wyjątek, trzecia dwa. Parametry typu out i inout wymagają w Javie dodatkowych klas pojemnikowych do ich transportu. Parametrom typu in odpowiadają zwykłe parametry Javy.

kompilator idlj

Do wygenerowania kodu Javy z pliku specyfikacji IDL służy program idlj. Jest on zawarty w dystrybucji SDK Javy i znajduje się w jej katalogu bin. Jako argument pobiera on nazwę pliku ze specyfikacją. Wynikiem jego działania jest zestaw plików niezbędnych do implementacji i uruchomienia klienta i serwera usługi zdefiniowanej w specyfikacji IDL.

W implementacjach CORBA innych producentów (a także we wcześniejszych wersjach J2SDK) kompilator ten może mieć inną nazwę.


3. Przykład - przesyłanie plików

Technologię CORBA zaprezentujemy na przykładzie programu służącego do przesyłania plików ze zdalnego serwera. Całość - zarówno klient jak i serwer - będą napisane w Javie. Korzystać będziemy z wersji 1.4 SDK. Sposób implementacji i uruchamiania we wcześniejszych wersjach jest trochę inny.

Przykład ten pokaże jak implementować serwery nietrwałe (transient) - takie, których działanie kończy się wraz z zakończeniem procesu lub wątku rodzicielskiego (tego, który je utworzył). CORBA umożliwia również tworzenie serwerów trwałych (persistent) - takich, które mogą kontynuować działanie po zakończeniu procesu lub wątku rodzica.

3.1. Budowa programu

Serwerem świadczącym usługi przesyłania plików będzie obiekt klasy FileServant, która będzie realizować zadania zdefiniowane w pliku interfejsu FileTransferFace.idl. Będzie ona dostarczać usług (za pośrednictwem publicznych metod) takich jak: listowanie zawartości katalogu, pobranie pliku lub informacji o pliku. Metoda main() inicjująca środowisko CORBA znajduje się w klasie FileServer, i jest to jedyna metoda tej klasy, bowiem klasa FileServer służy wyłącznie do uruchamiania serwisów.

Metoda main() klienta jest zawarta w klasie FileClient. Inicjuje ona środowisko CORBA i pobiera referencję do serwera. Na końcu tworzy obiekt klasy FileTransferGUI, który odpowiada za interakcję z użytkownikiem.

Oprócz powyższych klas, w skład programu serwera wejdzie mnóstwo klas i interfejsów powstałych podczas generowania kodu Javy z interfejsu IDL. Będą one umieszczone w pakiecie FileTransfer i jego podpakiecie FileInterfacePackage.

ORB używa standardowego portu 900 do komunikacji sieciowej. Ponieważ jest to numer zastrzeżony (wymaga uprawnień administratora do zainstalowania na nim usługi) użyjemy portu 2004. Będzie to wymagało podania dodatkowych opcji uruchomieniowych bądź dodatków w kodzie źródłowym.

3.2. Przygotowanie interfejsu w języku IDL

Pierwszą rzeczą, którą trzeba wykonać pracując z CORBA jest zdefiniowanie interfejsu obiektu zdalnego serwera. Ma on określać jaki operacje serwer będzie wykonywał na rzecz klientów. Jak już wcześniej wspomniano, interfejs taki zapisuje się w specjalnym języku IDL, który następnie tłumaczy przy pomocy zewnętrznego programu się na kod języka implementacji. Interfejs IDL może zawierać znacznie więcej informacji niż interfejs Javy: mogą się w nim znaleźć oprócz metod i stałych własne typy danych, które po wygenerowaniu kodu implementującego zostaną przetłumaczone na klasy i interfejsy Javy. W szczególności mogą to być nowe wyjątki.

Słowo interfejs jest w tym kontekście wieloznaczne. Używa się go w odniesieniu do:

Nasz interfejs IDL zawarty w pliku FileTransferFace.idl będzie zawierał moduł FileTransfer. Ponieważ moduły IDL odpowiadają pakietom Javy, wszystko co się w nim znajduje zostanie umieszczone w wygenerowanym pakiecie FileTransfer. W module znajdują się:

module FileTransfer {

  exception AccessDenied {
  };

  struct FInfo {
    boolean isDirectory;
    boolean accessible;
    long long size;
  };

  interface FileInterface {
    typedef sequence<octet>  Data;
    typedef sequence<wstring> Dir;
	
    void download(in wstring fileName, out Data fileData) raises(AccessDenied);
    void list(in wstring dirName, out Dir files) raises(AccessDenied);

    FInfo fstat(in wstring fname);
    boolean cpath(inout wstring path);
  };
};

Wyjątek AccessDenied zostanie przetłumaczony na klasę AccessDenied znajdującą się w pakiecie FileTransfer. Ze struktury FInfo również zostanie wygenerowana klasa o tej samej nazwie (również w pakiecie FileTransfer). Będzie ona zawierać jako publiczne atrybuty składniki określone w deklaracji struktury. Ich typy zostaną przekonwertowane zgodnie z zasadami podanymi wcześniej w tabelce. W szczególności typ long long odpowiada typowi long Javy.

Interfejs FileInterface zawiera deklaracje metod udostępnianych przez obiekt serwera klientowi. Zostanie z niego wygenerowany interfejs Javy, który klasa serwera obsługującego musi implementować. Znaczenie metod list() i download() powinno być oczywiste - jest listowanie zawartości katalogu w pierwszym przypadku i przesłanie zawartości pliku w tablicy bajtów w drugim. Metoda fstat() zwraca obiekt typu FInfo zawierający informacje o podanym pliku: czy jest katalogiem, czy serwer ma prawa do jego odczytu oraz rozmiar w przypadku plików. Metoda cpath() przekazuje serwerowi nazwę ścieżkową bieżącego (w rozumieniu klienta) katalogu a serwer zamienia ją na nazwę kanoniczną - pozbawioną składników postaci ".." i "." oznaczających katalog nadrzędny i bieżący. Na przykład ścieżka "/home/./foo/../bar" zostanie przekształcona na "/home/bar". Takiej zamiany nie może dokonać klient samodzielnie, ponieważ wymaga ona dostępu do systemu plików.

Zwróćmy uwagę na deklaracje parametrów tych metod. Te, które są poprzedzone słowem kluczowym in, oznaczają wartości przekazywane od obiektu wołającego (klienta) do obiektu, z którego metoda jest wywoływana (serwera). Są one w Javie traktowane jak zwykłe parametry i będą przekazywane przez wartość (będzie przekazana kopia obiektu!) do serwera. Parametry poprzedzone słowem kluczowym out oznaczają argumenty przekazywane od obiektu, na rzecz którego metoda została wywołana, do wołającego. W ciele metody musi nastąpić przypisanie wartości na ten parametr i po zakończeniu wykonywania metody wołający (klient) będzie mógł tę wartość odczytać. Java nie udostępnia takiej semantyki przekazywania parametrów, dlatego potrzebne są dodatkowe mechanizmy, aby ją zrealizować. Do przekazania takiej wartości od serwera do klienta jest wykorzystana dodatkowa klasa - pojemnik na wartość - która również zostanie wygenerowana. Szczegóły dalej, przy opisie implementacji interfejsu.
Trzecim rodzajem parametrów są te, poprzedzone słowem kluczowym inout. Jest to połączenie obu powyższych typów: taka wartość jest przekazywana od wołającego metodę klienta do obiektu, z którego metoda jest wywoływana (serwera), a następnie z powrotem do wołającego. W jej ciele wartość ta może zostać zmodyfikowana a wołający będzie mógł ją odczytać. Podobnie jak w przypadku parametrów typu out, Java nie obsługuje takiej semantyki przekazywania argumentów i wymagane są do jej realizacji dodatkowe mechanizmy (o czym dalej). Ostatnie dwa sposoby przekazywania argumentów wykorzystuje się do przekazywania wyników działania metody zamiast lub jako dodatek do zwracania rezultatu.

W interfejsie FileInterface zostały wprowadzone nowe typy danych. Słowo kluczowe sequence<> oznacza tablicę elementów, których typ jest podany w nawiasach kątowych. W tym przypadku Data będzie tablicą bajtów (octet odpowiada typowi byte Javy), a Dir będzie tablicą łańcuchów znakowych. Kompilator języka IDL wygeneruje z tych typów klasy Javy, których obiekty posłużą do przekazywania argumentów metod list() i download().

Zgłaszanie wyjątku przez metodę jest w IDL deklarowane słowem kluczowym raises(Exception); w nawiasie podaje się typ wyjątku. W Javie jest to równoważne klauzuli throws.

3.3. Wygenerowanie kodu Javy

Kompilator idlj zawarty w J2SDK wygeneruje z pliku interfejsu IDL zestaw klas i interfejsów Javy niezbędny do implementacji serwera i klienta. Jako argument podaje się nazwę pliku z interfejsem IDL. Dodatkowo użyjemy opcji -fall dzięki której zostaną wygenerowane zarówno pliki dla serwera jak i klienta.

Opcja -fclient spowoduje wygenerowanie tylko plików klienta, a -fserver - plików serwera.
idlj -fall FileTransferFace.idl
Po kompilacji w katalogu bieżącym powinien pojawić się katalog FileTransfer a w nim podkatalog FileInterfacePackage - oba zawierają mnóstwo plików. Nazwy tych katalogów są nazwami pakietów, w których są umieszczone wygenerowane klasy i interfejsy (pierwsza nazwa pakietu została określona w pliku interfejsu IDL). Oto zawartość tych katalogów:

Katalog bieżący:

FileTransfer/
FileTransferFace.idl
Katalog FileTransfer:
FileInterfacePackage/
AccessDenied.java                   *
AccessDeniedHelper.java
AccessDeniedHolder.java
FileInterface.java                  *
FileInterfaceHelper.java            *
FileInterfaceHolder.java
FileInterfaceOperations.java
FileInterfacePOA.java               *
FInfo.java                          *
FInfoHelper.java
FInfoHolder.java
_FileInterfaceStub.java
Katalog FileTransfer/FileInterfacePackage:
DataHelper.java
DataHolder.java                     *
DirHelper.java
DirHolder.java                      *
Gwiazdką zaznaczono klasy, z których będziemy bezpośrednio korzystać w kodzie serwera lub klienta. Oczywiście pozostałe są również w ogólnym przypadku niezbędne.

3.4. Implementacja serwera

Realizacja specyfikacji IDL

Po stronie serwera będziemy mieli dwie klasy: FileServant i FileServer. Druga z nich zawiera jedynie metodę main() uruchamiającą całe środowisko CORBA i tworzącą właściwy serwer będący obiektem pierwszej z nich. Klasa Fileservant dostarcza metod zadeklarowanych w interfejsie FileInterface. Formalnie rzecz biorąc nie implementuje ona tego interfejsu w sensie Javy, lecz dziedziczy abstrakcyjną klasę FileInterfacePOA wygenerowaną przez kompilator idlj.

Projektując klasę serwera, zamiast implementować interfejs o nazwie np. Nazwa określony w specyfikacji IDL, dziedziczymy klasę o nazwie NazwaPOA wygenerowaną przez kompilator idlj.

Uwaga ta dotyczy wyłącznie wersji CORBA korzystających z adaptera POA - Portable Object Adapter. Implementacje dostarczane z poprzednimi wersjami J2SDK używały innego adaptera obiektów (BOA - Basic Object Adapter) i sposób implementacji serwera był tam trochę inny.

Sygnatury metod

Oczywiście musimy wiedzieć, jakie są sygnatury metod zadeklarowanych w specyfikacji IDL, aby móc je zaimplementować w Javie. Gdzie znajduje się wygenerowany interfejs FileInterface wyspecyfikowany w pliku FileTransferFace.idl? Ponieważ został umieszczony w pakiecie FileTransfer (w IDL jest to moduł), to musi być w katalogu FileTransfer. Istotnie, ale niestety jest pusty. Wszystkie jego metody są zadeklarowane w interfejsie (Javy) FileInterfaceOperations.

Metody serwera, które w specyfikacji znalazły się w interfejsie Nazwa, kompilator idlj umieści w interfejsie (Javy) NazwaOperations. Nadklasa serwera NazwaPOA implementuje ten interfejs. Nie dostarcza ona jednak ich implementacji, dlatego jest abstrakcyjna.

Argumenty typu out i inout

Zatem projektując klasę serwera musimy zajrzeć do pliku FileTransfer/FileInterfaceOperations.java aby zobaczyć jak zostały przetłumaczone parametry metod. Te, poprzedzone słowem kluczowym in są traktowane jak normalne parametry metod w Javie. Inaczej ma się rzecz z parametrami typu out i inout, ponieważ po powrocie z wywołania wołający (klient) musi mieć możliwość pobrania wartości takiego argumentu, jaką miał on przed zakończeniem wykonywania ciała metody po stronie serwera. Java nie dostarcza takiej możliwości, dlatego potrzebne są dodatkowe obiekty opakowujące argumenty. Wołający przekazuje jako argument referencję do takiego obiektu. Jednak sama wartość, która ma być przekazana jest jego publicznym atrybutem. Adresat komunikatu (serwer) może go odczytywać i robić na ten atrybut przypisania. Po zakończeniu wywołania zmiany w obiekcie opakowującym zostaną odzwierciedlone po stronie wołającego. W ten sposób będzie on mógł odczytać jego (zapewne zmodyfikowaną) zawartość.

Klasy opakowujące (pojemniki) mają przyrostki Holder. Pojemnik opakowujący obiekt klasy Nazwa nazywa się NazwaHolder i ma publiczny atrybut value, który jest obiektem klasy Nazwa.

Klasy pojemnikowe dla standardowych typów Javy znajdują się w pakiecie org.omg.CORBA. Np. klasa opakowująca łańcuch znakowy String nazywa się org.omg.CORBA.StringHolder. Pojemniki na typy tablicowe zdefiniowane w interfejsie FileInterface znajdują się w wygenerowanym pakiecie FileInterfacePackage (zawartym w pakiecie FileTransfer). Są to DataHolder i DirHolder. Metoda serwera chcąc przesłać plik jako tablicę bajtów do klienta wykorzystując do tego argument typu out metody musi przypisać na atrybut value argumentu klasy DataHolder tablicę z zawartością. Tak samo jest z przekazywaniem tablicy napisów z zawartością katalogu: tu przypisujemy tablicę na atrybut value argumentu klasy DirHolder.

Parametr out lub inout metody określony w specyfikacji IDL jako typu Typ, po kompilacji do kodu Javy będzie typu TypHolder. Klasa tego parametru (TypHolder) albo jest w pakiecie org.omg.CORBA, albo zostanie wygenerowana.

Implementacja obiektu serwera

Teraz jesteśmy gotowi do przedstawienia kodu obiektu serwera FileServant. Jak wyjaśniono wyżej, jego klasa dziedziczy z FileInterfacePOA, która z kolei implementuje interfejs FileInterfaceOperations dostarczający sygnatury metod określonych w specyfikacji. W klasie FileServant znajdują się wyłącznie implementacje tych metod, choć tak być nie musi - mogą się w niej znaleźć również inne metody i atrybuty, jednak nie będą one dostępne dla klientów.
Importujemy wygenerowany pakiet FileTransfer, aby móc korzystać z klas utworzonych na podstawie specyfikacji IDL.

import java.io.*;
import org.omg.CORBA.*;

import FileTransfer.*;

public class FileServant
  extends FileInterfacePOA {

  public FInfo fstat(String fName){
    File file = new File(fName);
    FInfo finfo = new FInfo();
    finfo.isDirectory = file.isDirectory();
    finfo.accessible = file.canRead();
    finfo.size = file.length();
    return finfo;
  }

Metoda fstat() zwraca informacje o pliku podanym jako argument w obiekcie klasy FInfo. Jest to klasa wygenerowana ze specyfikacji IDL, której obiekty mają te informacje przechowywać.
Jeśli atrybut isDirectory ma wartość false, to nie oznacza to automatycznie, że jest zwykłym plikiem. W systemach Unix i podobnych plik może być np. urządzeniem, gniazdkiem czy kolejką FIFO.

  public boolean cpath(StringHolder path){
    try {
      File file = new File(path.value);
      path.value = file.getCanonicalPath();
    }
    catch(IOException ioe){
      System.err.println("IOException in getCPath: " + ioe.getMessage());
      return false;
    }
    return true;
  }

Metoda cpath() zamienia nazwę ścieżkową pliku (lub katalogu) na kanoniczną - pozbawioną redundantnych elementów postaci "." lub "..". Zgodnie ze specyfikacją IDL argument path jest typu inout: wołający przekazuje na nim napis jak normalny argument, a po zakończeniu wywołania odbiera wynik. Dlatego jest on typu pojemnikowego: StringHolder. Jak widzimy, sam łańcuch określający ścieżkę jest przekazywany na atrybucie value obiektu path. Jako rezultat metoda zwraca false jeśli konwersja się nie powiodła na skutek zgłoszonego wyjątku. true oznacza poprawne zakończenie wywołania.

  public void list(String dirName,
                   FileTransfer.FileInterfacePackage.DirHolder result
                   ) throws AccessDenied {
    File dir = new File(dirName);
    result.value = dir.list();
    if (result.value == null)
      throw new AccessDenied();
  }

Metoda list() przekazuje wołającemu tablicę plików zawartych w katalogu podanym jako argument dirName. Zawartość katalogu jest przekazana w tablicy łańcuchów znakowych będącej atrybutem value pojemnika DirHolder. Jeśli katalogu nie da się wylistować (np. z powodu braku praw dostępu) zostanie zgłoszony wyjątek AccessDenied. Klasa tego wyjątku została wygenerowana ze specyfikacji IDL, bo tam został on zadeklarowany.

  public void download(String fileName, 
                       FileTransfer.FileInterfacePackage.DataHolder result
                      ) throws AccessDenied {
    File file = new File(fileName);
    result.value = new byte[(int)file.length()];
    try {
      BufferedInputStream in = 
        new BufferedInputStream(new FileInputStream(file));
      in.read(result.value, 0, result.value.length);
      in.close();
    }
    catch(FileNotFoundException e){
      throw new AccessDenied();
    }
    catch(IOException e){
      System.err.println(e.getMessage());
    }
  }
}// class FileServant

Klasa FileServant została napisana tak, by jej metody mogły być wywoływane jednocześnie przez wiele wątków - nie posiada żadnych atrybutów. Może to być przydatne jeśli polityka dostępu do usługi świadczonej przez obiekt tej klasy będzie zezwalać na wielodostęp. Kontrolowaniem polityki jednoczesnego dostępu do usługi zajmuje się specjalny podsystem CORBA, jednak sposób jej definiowania wykracza poza ramy tego wykładu.

Najważniejsza metoda w tym programie - download() - pozwala pobrać zawartość pliku fileName jako tablicę bajtów. Podobnie jak w poprzednim przypadku jest ona przekazana w pojemniku typu DataHolder jako argument typu out. Klasa ta została wygenerowana ze specyfikacji IDL. Jeśli proces serwera nie ma praw do czytania podanego pliku (albo go po prostu nie ma) zostanie zgłoszony wyjątek FileNotFoundException, co zaskutkuje zgłoszeniem u wołającego wyjątku AccessDenied. Ważna jest kolejność klauzul catch przechwytujących wyjątki: klasa FileNotFoundException jest podklasą klasy IOException. Umieszczenie klauzul w odwrotnej kolejności spowodowałoby, przechwytywanie wyjątku FileNotFoundException przez klauzulę dla IOException i tym samym AccessDenied nie zostałby nigdy zgłoszony.

Mamy już klasę, której obiekt będzie bezpośrednio świadczył usługi na rzecz klientów, jednak aby do tego doszło potrzebne są jeszcze pewne czynności inicjalizacyjne - podobne jak przy pracy z RMI. Przeznaczymy do tego specjalną klasę.

Inicjalizacja systemu

Klasa FileServer będzie zajmować się zainicjowaniem składników systemu CORBA i rejestracją obiektu - serwera. Będzie zawierać wyłącznie metodę main(). Instrukcje w niej umieszczone wykonują standardowe dla tego typu serwisów CORBA czynności przedstawione w p. 2.3. W związku z tym można poniższy kod łatwo zmodyfikować w celu instalowania innych usług, czy też zwiększania ich liczby. Czynności te są podobne do wykonywanych podczas inicjowania serwera RMI, jednak jest ich więcej i są bardziej złożone, co ilustruje tabelka.

CzynnośćCORBARMI
Inicjalizacja systemu ORB.init() createRegistry() - Utworzenie rejestru (opcjonalne), możliwe spoza kodu programu.
Aktywacja warstwy pośredniej POA -
Utworzenie obiektu serwera + +
Pobranie referencji do serwera nazw uzyskanie dostępu do usługi "NameService" nie jest konieczne - wykonuje to automatycznie metoda rejestrująca obiekt
Zarejestrowanie nazwy++
Podtrzymanie wątku serwera ORB.run() Robi to rejestr

Numer portu można też podać jako argument wywołania programu -ORBInitialPort 2004.
Na początku metody main() definiujemy niestandardowy port, na którym będzie nasłuchiwać podsystem ORB, ponieważ domyślny port 900 może uniemożliwić zainstalowanie tej usługi użytkownikom bez uprawnień administracyjnych. Metoda ORB.init() zwraca referencję do jedynego obiektu klasy ORB, który umożliwi dostęp do jej usług. Jako pierwszy argument tej metody podaje się zwykle argumenty wywołania programu, którymi można wpływać na jej funkcjonalność (np. numer portu). Jeśli nie definiujemy żadnych właściwości Properties, to jako drugi argument metody ORB.init() podaje się null.

import java.io.*;
import java.util.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;

import FileTransfer.*;

public class FileServer {

  public static void main(String[] args){
    try {
      // określenie numeru portu ORB
      Properties orbprop = new Properties();
      orbprop.put("org.omg.CORBA.ORBInitialPort", "2004");

      // utworzenie i inicjalizacja ORB
      ORB orb = ORB.init(args, orbprop);
	    
      // pobranie referencji do głównego POA
      org.omg.CORBA.Object poaref =
        orb.resolve_initial_references("RootPOA");
      POA rootpoa = POAHelper.narrow(poaref);

      // aktywacja zarządcy POA
      rootpoa.the_POAManager().activate();

      // utworzenie serwera plików
      FileServant servant = new FileServant();

      // utworzenie referencji do obiektu serwera
      org.omg.CORBA.Object seref =
        rootpoa.servant_to_reference(servant);
      FileInterface fi = FileInterfaceHelper.narrow(seref);

      // pobranie referencji do serwera nazw
      org.omg.CORBA.Object nameref =
        orb.resolve_initial_references("NameService");
      NamingContextExt naming = NamingContextExtHelper.narrow(nameref);
	    
      // powiązanie nazwy z referencją do obiektu
      NameComponent path[] = naming.to_name("FileTransfer");
      naming.rebind(path, fi);

      System.out.println("Server started...");
	    
      // oczekiwanie na zgłoszenia
      orb.run();
    }
    catch(Exception e){
      System.out.println("Server ERROR: " + e.getMessage());
      e.printStackTrace();
    }
    System.out.println("Server exiting...");
  }
}
Metoda resolve_initial_references() z klasy ORB zwraca referencję do obiektu świadczącego usługę o nazwie podanej jako argument. Typem wyniku jest org.omg.CORBA.Object, który jest odpowiednikiem ogólnego typu java.lang.Object dla obiektów CORBA, dlatego wymaga rzutowania. Wykonują je metody narrow() z klas kończących się przyrostkiem Helper. Metoda resolve_initial_references() jest przede wszystkim potrzebna do uzyskania dostępu do usługi nazewniczej oraz adaptera obiektu POA, który pośredniczy w przesyłaniu żądań do obiektu serwera od klientów. Musi on zostać aktywowany metodą activate() zanim nadejdą zgłoszenia. Metoda to_name() analizuje złożoną nazwę i rozbija ją na nazwy proste zwracając je w tablicy jako wynik (nazwy, pod którymi rejestruje się usługi CORBA mogą mieć złożoną, hierarchiczną budowę). Taką tablicę przekazuje się metodzie rebind(), która wiąże nazwę z obiektem serwera przekazanym jako argument. Metoda run() podtrzymuje wątek w oczekiwaniu na zgłoszenia nadchodzące do pośrednika ORB. Będzie on aktywny dopóki nie zostanie zewnętrznie zakończony.

3.5. Implementacja klienta

Program klienta będzie składał się z dwóch klas:

Oczywiste jest, że zarówno serwer jak i klient muszą działać na tym samym porcie.

Pierwsze instrukcje metody main() mają na celu - tak jak w przypadku serwera - zmianę domyślnego portu na 2004. Można to zrobić również podając argument -ORBInitialPort 2004 podczas wywoływania maszyny wirtualnej.

Inaczej niż w RMI, nie dołączamy do nazwy usługi położenia obiektu serwera w postaci przedrostka z nazwą komputera. Adres (bądź nazwę) hosta, z którym chcemy się połączyć podaje się np. jako argument wywołania programu -ORBInitialHost host.
Dalej następują już niezbędne czynności prowadzące do pozyskania referencji do obiektu serwera. Najpierw - podobnie jak w kodzie serwera - inicjujemy pośrednika ORB i pobieramy referencję do usługi nazywania. Za jej pomocą uzyskujemy referencję do naszego serwera plików podając jako nazwę usługi dokładnie taki sam łańcuch jak ten, pod którym serwer został zarejestrowany.

import java.io.*;
import java.util.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;

import FileTransfer.*;

public class FileClient {

  public static void main(String[] args){
    try {
      // określenie numeru portu ORB
      Properties orbprop = new Properties();
      orbprop.put("org.omg.CORBA.ORBInitialPort", "2004");

      // utworzenie i inicjalizacja ORB
      ORB orb = ORB.init(args, orbprop);

      // pobranie referencji do serwera nazw
      org.omg.CORBA.Object nameref =
        orb.resolve_initial_references("NameService");
      NamingContextExt naming = NamingContextExtHelper.narrow(nameref);

      // pobranie referencji do obiektu obsługującego
      org.omg.CORBA.Object seref =
        naming.resolve_str("FileTransfer");
      FileInterface fi = FileInterfaceHelper.narrow(seref);

      System.out.println("Uzyskano referencję do zdalnego obiektu");
	    
      new FileTransferGUI(fi);
    }
    catch(Exception e){
      System.out.println(e.getMessage());
      e.printStackTrace();
    }
  }
}

Na końcu metody startowej main() klienta (zawartej w klasie FileClient) tworzymy obiekt klasy FileTransferGUI przekazując konstruktorowi odniesienie do zdalnego obiektu serwera (a właściwie jego namiastki). Od tego momentu użytkownik interaktywnie wywołuje metody serwera za pośrednictwem utworzonego obiektu klasy FileTransferGUI.

Po uruchomieniu klienta ukazuje się okno typu JFrame z listą JList prezentującą zawartość katalogu uruchomieniowego serwera. Po kliknięciu w nazwę otwiera się okno dialogowe z informacjami o pliku bądź katalogu i zapytaniem czy

Pobrane pliki są zapisywane w katalogu uruchomieniowym klienta.

Klasa FileTransferGUI posiada następujące atrybuty:

Oczywiście metody serwera będą wywoływane na rzecz referencji fin. Katalog cdir będzie przechowywał ścieżkę absolutną (od korzenia) w postaci kanonicznej dzięki metodzie cpath() serwera. Jej początkowa wartość "." zostanie zamieniona na absolutną po pierwszym wylistowaniu zawartości katalogu serwera.

import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import org.omg.CORBA.*;

import FileTransfer.*;

public class FileTransferGUI 
  extends JFrame {

  private JList list;
  private FileInterface fin;
  private String cdir = ".";     

  private MouseAdapter ma = 
    new MouseAdapter(){
      public void mouseClicked(MouseEvent me){
	    
        String fname = (String)list.getSelectedValue();
        String path = cdir + (cdir.charAt(0) == '/' ? "/" : "\\") + fname;
        FInfo finfo = fin.fstat(path);
        if (!confstat(path, finfo))
          return;
        if (finfo.isDirectory)
          ls(path);
        else
          download(path, fname);
	}
    };

Kliknięcie myszką w listing katalogu powoduje wywołanie metody mouseClicked() słuchacza, w której:

  1. pobieramy nazwę zaznaczoną nazwę na zmienną fname
  2. Znak '\' w łańcuchach znakowych musi być powtórzony dwukrotnie ("\\"), ponieważ ma specjalne znaczenie.
    Na zmiennej path tworzymy absolutną nazwę ścieżkową zaznaczonego pliku poprzez doklejenie do fname bieżącego katalogu cdir i separatora nazw (znak '\' lub '/') obowiązującego w systemie serwera. Aby go poznać sprawdzamy pierwszy znak ścieżki absolutnej: jeśli jest to '/' - mamy do czynienia z Unixem, w przeciwnym wypadku Windows.
  3. Następnie pobieramy z serwera informacje dotyczące zaznaczonego pliku wywołując metodę fstat()
  4. Metoda confstat wyświetla okno dialogowe pokazane na obrazku. Wybranie opcji innej niż YES powoduje zwrócenie wartości false i zakończenie wykonywania metody mouseClicked().
  5. Jeśli zaakceptowano wybór, to zależnie od tego, czy nazwa odnosi się do katalogu czy nie, listujemy jego zawartość metodą ls() (wywołującą list() z serwera) lub pobieramy plik metodą download() (również wywołującą metodę serwera o tej samej nazwie).

Konstruktor klasy FileTransferGUI tworzy GUI, czego nie będziemy tu szczegółowo omawiać. Wywołanie metody ls() powoduje pobranie z serwera (metodą list()) zawartości jego katalogu uruchomieniowego i uwidocznienie jej na liście list.

  public FileTransferGUI(FileInterface fi){
    fin = fi;

    Container cp = getContentPane();
    list = new JList();
    JScrollPane spane = new JScrollPane(list);
    ls(".");
    list.addMouseListener(ma);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    spane.setPreferredSize(new Dimension(300, 300));

    cp.add(spane);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocation(300, 300);

    pack();
    show();
  }

  private DefaultListModel makeModel(String[] files){
    Arrays.sort(files);
    DefaultListModel dlm = new DefaultListModel();
    dlm.addElement("..");
    for (int i = 0; i < files.length; i++)
      dlm.addElement(files[i]);
    return dlm;
  }

Metoda makeModel() przygotowuje model danych dla listy list (typu JList). Przekształca ona zawartość katalogu pobraną z serwera jako tablicę łańcuchów znakowych do postaci obiektu klasy DefaultListModel, którą można uwidocznić na liście list.

Metoda confstat() wyświetla okno dialogowe z informacjami dotyczącymi bieżącego pliku/katalogu i zapytaniem o wykonanie akcji zależnej od rodzaju pliku: jeśli jest to katalog - zostanie pobrana i wyświetlona jego zawartość; w innym wypadku nastąpi pobranie zawartości pliku.

  private boolean confstat(String fpath, FInfo finfo){
    String msg = fpath + "\n" + 
                "is" + (finfo.accessible ? " " : " not ") + "readable\n";
    if (finfo.isDirectory)
      msg = "Directory \n" + msg + "list ?";
    else
      msg = "File \n" + msg + "size = " + finfo.size + " bytes\ndownload ?";

    int option = JOptionPane.showConfirmDialog(this, msg);
    if (option == JOptionPane.YES_OPTION)
      return true;
    else
      return false;
  }

  private void download(String path, String local){
    // utworzenie obiektu przechowującego argument typu out
    FileTransfer.FileInterfacePackage.DataHolder result =
      new FileTransfer.FileInterfacePackage.DataHolder();
    try {
      // wywołanie metody obiektu zdalnego
      fin.download(path, result);
      // zapisanie wyniku do pliku
      BufferedOutputStream out = 
        new BufferedOutputStream(new FileOutputStream(local));
      out.write(result.value, 0, result.value.length);
      out.flush();
      out.close();
    }
    catch(AccessDenied ade){
      JOptionPane.showMessageDialog(this, path + ": Acces denied");
    }
    catch(IOException e){
      System.out.println(e.getMessage());
    }
  }

Metody ls() i download() opakowują metody list() i download() serwera, wykonując dodatkowe czynności. Pierwszym argumentem (path) powyższej metody download() jest absolutna nazwa ścieżkowa pliku na serwerze, drugim (local) - jego nazwa (bez katalogów nadrzędnych), pod którą zostanie zapisany lokalnie. Pierwszą czynnością jaką trzeba wykonać przed wywołaniem metody download() serwera jest utworzenie pojemnika na argument typu out jakim jest drugi argument tej metody. Będzie nim obiekt result klasy DataHolder wygenerowanej przez kompilator języka IDL. Po powrocie z wywołania tej metody jego atrybut value (typu byte[]) będzie przechowywał zawartość pliku path umieszczoną tam przez obiekt serwera. Zapisujemy ją do pliku local metodą write() z klasy BufferedOutputStream. Wyjątek AccessDenied może zostać zgłoszony na serwerze, jeśli nie mamy praw do czytania podanego pliku. Drugi z przechwytywanych wyjątków może zostać zgłoszony lokalnie podczas zapisu pliku.

Metoda ls() wywołuje najpierw metodę cpath() serwera aby poznać kanoniczną nazwę ścieżkową pliku, która następnie będzie umieszczona w tytule głównego okna aplikacji. Metoda ta ma parametr typu inout. Obiekt, na rzecz którego metodę wołamy, pobiera na nim jakąś wartość a przy wyjściu z metody zapisuje tam nową wartość jako wynik. Będzie on dostępny dla wołającego. Aby tak się jednak stało musimy, podobnie jak w poprzedniej metodzie, przygotować pojemnik na przekazywaną wartość. Ponieważ chcemy przekazywać łańcuch znakowy typu String - korzystamy ze standardowego pojemnika StringHolder (z pakietu org.omg.CORBA zawierającego pojemniki na standardowe typy). Początkowa wartość przekazywana do serwera jest podawana w konstruktorze. Jeśli wywołanie metody cpath() zakończyło się niepowodzeniem (na skutek zgłoszonego i przechwyconego w niej wyjątku) zwróci ona wartość false i na zmiennej cpath zapamiętamy wartość dirName (która nie jest kanoniczna). Jeśli wywołanie się powiodło, to wartość results.value zapamiętujemy na zmiennej cpath i przekazujemy jako pierwszy argument do metody list() serwera. Drugim argumentem jest utworzony pojemnik na tablicę napisów typu DirHolder (ta klasa została wygenerowana przez kompilator języka IDL). Wywołanie metody list() może zostać przerwane zgłoszeniem wyjątku AccessDenied, dlatego jest ujęte w blok try-catch. Po zakończeniu wywołania zapamiętujemy ścieżkę path na atrybucie cdir oraz umieszczamy ją w tytule okna i wymieniamy zawartość listy list.

  private String[] ls(String dirName){
    // utworzenie pojemnika na argument typu inout dla cpath()
    StringHolder pathold = new StringHolder(dirName);
    String cpath = (fin.cpath(pathold) ? pathold.value : dirName);
    // utworzenie pojemnika na argument typu out dla list()
    FileTransfer.FileInterfacePackage.DirHolder results =
      new FileTransfer.FileInterfacePackage.DirHolder();
    try {
      fin.list(cpath, results);
      cdir = cpath;
      setTitle(cpath);
      list.setModel(makeModel(results.value));
      list.setSelectedIndex(0);
    }
    catch(AccessDenied ade){
      JOptionPane.showMessageDialog(this, cpath + ": Access denied");
    }
    return results.value;
  }
}

Jeśli proces serwera nie ma praw do listowania zawartości katalogu dirName, zostanie zgłoszony wyjątek AccessDenied.

3.6. Kompilacja

Zakładając, że zawartość bieżącego katalogu jest taka jak określono w p. 3.3 - po wygenerowaniu plików ze specyfikacji - możemy przystąpić do kompilacji. Najpierw oczywiście trzeba skompilować pliki wygenerowane, potem klasy serwera i klienta w dowolnej kolejności.

javac FileTransfer/FileInterfacePackage/*.java
javac FileTransfer/*.java
javac *.java
Zawartość katalogu po kompilacji jest przedstawiona na obrazku.

3.7. Uruchomienie

Demon orbd tworzy podkatalog orb.db w katalogu, z którego został uruchomiony.

Napierw uruchamiamy program orbd - Object Request Broker Daemon. Jest to implementacja pośrednika ORB dostarczana wraz z SDK Javy. Ponieważ przyjęliśmy, że pracujemy na porcie 2004, musimy poinformować go o tym odpowiednią opcją:

orbd -ORBInitialPort 2004 
Następnie uruchamiamy program serwera:
Jeśli właściwość org.omg.CORBA.ORBInitialPort nie zostanie przekazana do metody init() pośrednika ORB, to należy podać numer portu opcją -ORBInitialPort 2004 zarówno serwerowi jak i klientowi.
java FileServer
A potem - z innej konsoli - klienta:
java FileClient
W ten sposób zarówno klient jak i serwer działają na tym samym komputerze. Skorzystanie z serwera działającego na innym niż klient komputerze jest bardzo proste: wystarczy podać klientowi jako argument wywołania odpowiednią opcję z jego nazwą.
Nie podajemy w ten sposób opcji maszyny wirtualnej, lecz argument wywołania programu klienta przekazywany do metody main() i dalej do metody ORB.init().
java FileClient -ORBInitialHost anyhost.anywhere
Oczywiście na komputerze anyhost.anywhere musi działać usługa orbd oraz serwer FileServer (uwaga na numery portów!).

Tego programu nie można uruchomić w środowisku CORBA pochodzącym z wersji SDK Javy 1.3.


4. Podsumowanie

CORBA powstała w 1991 roku. Obecnie trwają prace nad wersją 3.0 specyfikacji. Implementacja zawarta w J2SDK 1.4 jest zgodna z wersją 2.3.1. Dla uproszczenia w wykładzie ograniczyliśmy się wyłącznie do języka Java. Należy jednak pamiętać, że istotą technologii CORBA jest integracja oprogramowania napisanego w różnych językach. Zarówno klient jak i serwer z przedstawionego przykładu może być zaimplementowany w dowolnym języku, dla którego istnieje odwzorowanie z języka IDL. Aby móc programować korzystając z systemu CORBA musimy dysponować jego implementacją dla danego języka, w szczególności kompilatorem języka IDL oraz pośrednikiem ORB.

4.1. CORBA a inne technologie

Zastosowanie architektury CORBA jest początkowo kosztowniejsze od tradycyjnych technik programowania rozproszonego (gniazda, RPC, RMI) ze względu na jej dużą złożoność. Ale ta złożoność jest spowodowana jej ogromnymi możliwościami i elastycznością. W przypadku dużych lub długoterminowych aplikacji ten narzut zwraca się z nawiązką. Łatwiejsze będą np. modyfikacje działającego oprogramowania czy integracja z dodatkowymi modułami, dodawanie nowych usług.

4.2. Dostępne implementacje

Wielu producentów oprogramowania dostarcza implementacje systemu CORBA dla różnych języków programowania. W tabelce umieszczono zestawienie niektórych wersji posiadających wsparcie dla języka Java.

NazwaProducent
Java 2 ORBSun
VisiBrokerInprise Corporation
OrbixWebIona Technologies
Netscape Communicatorwersja VisiBroker zawarta w przeglądarkach
WebSphereIBM

W systemie Linux jest dostępna implementacja standardu CORBA o nazwie ORBit, będąca częścią projektu GNU. Jest używana przez środowisko GNOME. Językiem implementacji jest C.


Dokumentacja i literatura

www.omg.org
Witryna organizacji OMG. Podstawowe źródło wiedzy o technologii CORBA.
Opis języka IDL i pokrewnych tematów z J2SDK. Zawiera krótki podręcznik.
Dostępny w dokumentacji Javy jako docs/guide/idl/index.html (wersja on-line)
Opis technologii CORBA dla Javy
Dostępny w dokumentacji Javy jako docs/guide/corba/index.html (wersja on-line)
Informacje zawarte w opisie klas pakietu org.omg.CORBA
Dostępne w dokumentacji API Javy jako docs/api/org/omg/CORBA/package-summary.html (wersja on-line)