Java i XML, programowanie, Java


10

XML-RPC

W tym rozdziale zostanie omówione kolejne ciekawe zastosowanie języka XML — model żądań i odpowiedzi XML-RPC. XML-RPC to po prostu specyficzna odmiana RPC, czyli technologii wy­wo­ływania zdalnych procedur. Jeśli Czytelnik nie jest doświadczonym programistą, bądź też do tej po­ry posługiwał się tylko językiem Java, pojęcie zdalnego wywoływania procedur może być mu obce; dla innych zaś poruszanie tego tematu może wydać się dziwne — RPC w ostatnich czasach stracił na popularności. W trakcie lektury niniejszego rozdziału Czytelnik dowie się, dlaczego te trzy nie­po­zorne literki przed skrótem RPC zrewolucjonizowały coś, co wydawało się być dinozau­rem w świe­cie programistycznym. Czytelnik nauczy się również korzystać z XML-RPC z poziomu pro­gramów w Javie. Zakończenie rozdziału poświęcone jest praktycznym zastosowaniom modeli XML-RPC.

Jeśli Czytelnik poddał się fali programowania obiektowego, tak popularnego w ostatnich latach, to pewnie wzdryga się na dźwięk słowa „procedura”. Języki proceduralne, takie jak PL/SQL czy ANSI C, nie są „modne” i jest ku temu wiele powodów. Zapewne niejednokrotnie Czytelnikowi zdarzyło się „oberwać” za nazwanie metody Javy funkcją lub procedurą; wie też, jak unikać „kodu spaghetti” — czyli kodu polegającego na łączeniu wielu metod w jeden długi łańcuch. Wraz z ty­mi językami i sposobami programowania odsunięto na bok technologię RPC — nowe, obie­ktowe techniki umożliwiają osiągnięcie tych samych rezultatów przy bardziej przejrzystej strukturze programu i większej wydajności. Jednak, co ciekawe, popularyzacja XML-a przyczyniła się do upowszechnienia interfejsów API zbudowanych z myślą o XML-RPC oraz do coraz częstszego wykorzystywania XML-RPC mimo niedobrych skojarzeń, jakie się z tym skrótem wiążą.

Zanim Czytelnik zacznie stosować te interfejsy, powinien przyjrzeć się technologii RPC i porów­nać ją z podobnymi rozwiązaniami Javy, a szczególnie z technologią RMI (Remote Method Invo­cation). Jeśli zdecydujemy się na wykorzystanie XML-RPC w aplikacji (a na pewnym etapie pracy z pewnością tak się stanie), na pewno będziemy musieli uzasadnić swój wybór innym progra­mi­stom — szczególnie tym, którzy mają właśnie za sobą lekturę o EJB czy RMI. Wszystkie te technologie mają na pewno swoje miejsce, ale zrozumienie właściwego ich stosowania jest bardzo isto­tne. Najbardziej popularnymi sposobami operowania na obiektach poprzez sieć są RPC i RMI.

RPC kontra RMI

Technologia RMI zyskuje ogromną popularność wśród programistów Javy. Cała specyfikacja EJB (Enterprise JavaBeans) została oparta na bazie RMI; umiejętność pisania trzywarstwowych apli­ka­cji RMI jest koniecznością. Jeśli Czytelnik jeszcze tego nie potrafi, powinien sięgnąć po Java Enterprise in a Nutshell (autorzy: David Flanagan, Jim Farley, William Crawford i Kris Ma­gnusson) lub Java Distributed Computing Jima Farleya (obie książki wydawnictwa O'Reilly & As­sociates).

Co to jest RMI?

Technologia RMI to wywoływanie zdalnych metod (remote method invocation). Koncepcja wydaje się dość prosta — RMI umożliwia wywoływanie metod na obiekcie znajdującym się na innym komputerze niż program. Na tym opiera się całe programowanie rozproszone w Javie i to właśnie stanowi szkielet technologii EJB oraz wielu implementacji aplikacji dla przedsiębiorstw. Bez za­głę­biania się w detale można powiedzieć, że RMI za pomocą procedur pośredniczących (ang. stub) oraz szkieletów opisuje metody dostępne do zdalnego wywołania. Klient wykorzystuje owe pro­cedury pośredniczące (zazwyczaj mające postać interfejsów Javy), a RMI obsługuje całą „magię” tłumaczenia żądań wysyłanych do procedury pośredniczącej na wywołania sieciowe. Metoda działa na faktycznym obiekcie komputera zdalnego; wynik przesyłany jest z powrotem drogą sie­ciową. Na koniec procedura pośrednicząca zwraca wynik klientowi, który jako pierwszy wywołał metodę, i klient może działać dalej. Przede wszystkim trzeba pamiętać, że klienta „nie interesują” szczegółowe zasady działania RMI i sieci; procedura pośrednicząca wykorzystywana jest tak, jak gdyby był to faktyczny obiekt implementujący określone metody. Technologia RMI (za pomocą protokołu zdalnego JRMP™) działa tak, że cała komunikacja sieciowa odbywa się poza kulisami; klient ma dostęp do ogólnego wyjątku (java.rmi.RemoteException), a programista może skupić się na regułach biznesowych i logice aplikacji. Technologia RMI umożliwia również ko­rzys­tanie z różnych protokołów (np. Internet Inter-ORB Protocol) — dzięki temu można uru­cho­mić komunikację pomiędzy obiektami Java i CORBA, często także w innych językach, np. C lub C++.

Technologia RMI ma również jednak pewne wady. Po pierwsze, intensywnie wykorzystuje za­soby. Używanych jest całkiem sporo klas i choć stanowią one standardowe wyposażenie pakietu JDK, to przecież stworzenie ich egzemplarza pochłania pamięć i inne zasoby. Protokół JRMP cha­rakteryzuje się słabą wydajnością, a zastąpienie go wcale nie jest prostym zadaniem. Kiedy klienty wysyłają żądania RMI, muszą zostać otwarte i utrzymane gniazda, a im jest ich więcej, tym bar­dziej spada wydajność systemu — szczególnie wtedy, gdy system dostępny jest w sieci (wtedy dla dostępu HTTP konieczne jest otworzenie dalszych gniazd). Technologia RMI wymaga również istnienia serwera lub usługodawcy, do których można dowiązać obiekty. Dopóki obiekt nie zo­sta­nie dowiązany do nazwy odpowiadającej takiemu usługodawcy, dopóty nie będzie dostępny z po­zio­mu innych programów. To wymaga użycia rejestru RMI, serwera usług katalogowych LDAP lub innych usług typu Java Naming and Directory Interface (JNDI). I jeszcze jedno — korzystanie z RMI może wiązać się z koniecznością pisania dużej ilości kodu, nawet biorąc pod uwagę to, jak wiele pomocnych klas udostępnia JDK. Konieczne jest zaprogramowanie zdalnego interfejsu opi­su­jącego metody dostępne do wywołania oraz (jeśli korzystamy z EJB) szeregu innych inter­fej­sów. Oznacza to również, że oddanie kolejnej metody do klasy serwera powoduje zmianę interfejsu i przekompilowanie procedur pośredniczących, co często nie jest pożądane (a niejednokrotnie jest po prostu niemożliwe).

Co to jest RPC?

Technologia RPC to wywoływanie zdalnych procedur (remote procedure calls). RMI umożliwia bezpośrednie działanie na obiekcie Javy, natomiast RPC pozwala korzystać z metod samodziel­nych (tak, tutaj nazywane są one procedurami!) za pośrednictwem sieci. To ogranicza interaktywność, ale upraszcza interfejs, z którym komunikuje się klient. Technologię RPC można sobie wyobrazić jako sposób korzystania z „usług” komputerów zdalnych, z kolei RMI umożliwia korzystanie z „ser­werów”. Z tej subtelnej różnicy wynika fakt, że proces RMI jest zazwyczaj sterowany w całości przez klienta — zdarzenia następują po zdalnym wywołaniu metod. RPC to częściej klasa lub zestaw klas wykonujących zadania bez interwencji klienta; jednakże czasami klasy te obsługują żądania przesłane przez klienty i wykonują ich „minizadania”. Przedstawione w dalszej części roz­działu przykłady pomogą zrozumieć te teoretyczne wywody.

RPC nie jest środowiskiem tak interaktywnym jak RMI, ale oferuje szereg zalet względem tego ostatniego. Umożliwia współpracę oddzielnych systemów. Technologia RMI pozwala co prawda na łączenie Javy z serwerami i klientami CORBA (poprzez IIOP), ale RPC umożliwia komu­ni­ka­cję dosłownie dowolnych aplikacji — protokołem transportowym może być bowiem HTTP. Po­nie­waż niemal dowolny wykorzystywany obecnie język oferuje sposób komunikacji za pośrednictwem HTTP, RPC stanowi niezwykle atrakcyjne rozwiązanie w przypadku tych zastosowań, w których konieczne jest porozumiewanie się z systemami zastanymi. RPC jest zazwyczaj również mniej „zasobożerny” niż RMI (szczególnie wtedy, gdy do kodowania wykorzystywany jest XML — o czym za chwilę); RMI często wymaga przesłania przez sieć całych klas Javy (np. kodów apletów czy własnych klas pomocniczych EJB), a RPC musi tylko przekazać przez sieć parametry żądania i wy­nik działania, zazwyczaj w postaci danych tekstowych. RPC pasuje również świetnie do modelu interfejsów API — systemy nie stanowiące części określonej aplikacji i tak mogą pobierać z niej informacje; oznacza to, że zmiany poczynione w serwerze nie przekładają się na zmiany w kodzie aplikacji klientów. Przy transferze opartym na zwykłym tekście dodatkowe metody można do­da­wać bez konieczności rekompilacji klienta; użycie tych metod wymaga zaś tylko niewielkich zmian.

Odwieczny problem z technologią RPC związany jest z kodowaniem przesyłanych danych. Wy­obraźmy sobie, że chcemy stworzyć tekstową i niewielką objętościowo reprezentację struktur Javy Hastable lub Vector. Jeśli weźmiemy pod uwagę, że te struktury mogą z kolei zawierać inne obiekty Javy, taka reprezentacja nagle przestaje być zadaniem banalnym; stworzony format musi ponadto nadawać się do użycia przez dowolne języki programowania — inaczej umniejszymy ko­rzyści płynące z zastosowania technologii RPC. Do niedawna zachodziła odwrotnie proporcjonal­na relacja pomiędzy jakością i użytecznością kodowania w RPC a jego prostotą; innymi słowy, im prościej reprezentowane były złożone obiekty, tym trudniej było wykorzystać takie kodowanie w wielu językach bez tworzenia dodatkowego, własnego kodu. Rozbudowane tekstowe reprezen­ta­cje danych nie zostały ustandaryzowane i wymagały całkowicie nowych implementacji w po­szczególnych językach. W tej chwili Czytelnik zapewne domyśla się już, do czego zmierzam.

XML-RPC

Największą przeszkodą w używaniu technologii RPC był zawsze sposób kodowania. Kiedy pojawił się XML-RPC, wszystko uległo zmianie. XML oferował nie tylko prostą, tekstową re­pre­zen­tację danych; stanowił także standard strukturyzacji danych. Kiedy grupa W3C opublikowała specyfikację XML 1.0, zniknęły obawy o konieczność stosowania rozwiązań własnych — korzy­s­ta­jący z RPC mieli pewność, że XML w najbliższym czasie nie sprawi im żadnej niespodzianki. Co więcej, standard SAX stworzył możliwość uzyskania dostępu do XML-a w sposób wydajny i stan­dardowy; to uprościło implementację bibliotek RPC. Pozostała więc już tylko do uruchomienia tran­smisja poprzez HTTP (coś, co robimy już od ponad 10 lat) oraz specyficzne interfejsy kodowania i dekodowania. Po kilku implementacjach beta bibliotek XML-RPC stało się jasne, że oprócz pozostałych zalet, XML jest również szybki i mało wymagający — wydajność bibliotek XML-RPC była większa niż oczekiwano. Model XML-RPC stanowi obecnie prężne i stabilne roz­wiązanie zdalnego wywoływania procedur.

Czytelnikowi, jako programiście Javy, XML-RPC oferuje sposób prostego tworzenia „punktów zaczepienia” w aplikacji i usługach, zarówno do użytku własnego, jak i do wykorzystania przez klienty aplikacji w różnych oddziałach, a nawet firmach. Interfejsy API są odseparowane od Javy, klienty nie muszą więc korzystać bezpośrednio z tego języka. Ponadto dzięki XML-RPC nie trze­ba już się uczyć o RMI, aby korzystać z usług rozproszonych (przynajmniej na początku). Ni­niejszy rozdział stanowi opis implementacji serwera i klienta XML-RPC. Czytelnik przekona się również, że serwer może działać niezależnie od klientów, wciąż jednak udostępniając interfejsy współpracujące z XML-RPC i pozwalając na komunikację i pobieranie danych. Porównanie te­ch­no­logii RMI z XML-RPC pozwoli wykazać wyższość tego ostatniego w wielu zastosowaniach.

Powiedział „Witaj!”

Jeśli Czytelnikowi udało się przebrnąć przez te kilka stron wywodów o zdalnym wywoływaniu pro­cedur, z pewnością jest przekonany, że XML-RPC to narzędzie przydatne i że może rozwiązać wiele problemów programistycznych. Aby rozwinąć temat, spróbujemy teraz zbudować praw­dzi­wy program wykorzystujący tę technologię. Zgodnie z tradycją, zaczniemy od prostego programu „Wi­taj świecie!”. W naszym serwerze XML-RPC zostanie zarejestrowana procedura ob­sługi. Pro­cedura ta pobiera parametr String Javy (nazwę użytkownika) i zwraca łańcuch za­wie­ra­jący „Wi­taj” oraz pobraną nazwę, np. „Witaj Brett”. Następnie procedura ta zostanie udo­stęp­niona przez serwer klientom XML-RPC. Na koniec stworzymy prostego klienta łączącego się z ser­we­rem i żądającego wywołania tej metody.

W rzeczywistości serwer i procedura obsługi XML-RPC znajdowałyby się na jednym komputerze (zazwyczaj wydajnym serwerze), a klient na innym i cała operacja odbywałaby się zdalnie. Jed­nakże, jeśli nie mamy pod ręką kilku komputerów, przykłady możemy przećwiczyć lokalnie. Pro­ces bę­dzie przebiegał wtedy szybciej niż w rzeczywistym zastosowaniu, ale i tak będzie można zaob­ser­wować, jak poszczególne elementy układają się w jedną całość i jak ten cały XML-RPC działa.

Skąd wziąć biblioteki XML-RPC?

Jak to zostało powiedziane wcześniej, w rozwój RPC, a ostatnio XML-RPC, włożono już wiele pracy. Podobnie jak w przypadku używania interfejsów SAX, DOM i JDOM do obsługi XML-a, nie trzeba drugi raz wynajdywać koła — na pewno istnieją dobre, a nawet świetne pakiety Javy spełniające nasze potrzeby. Centrum informacji o XML-RPC, a także zasób odsyłaczy do od­po­wied­nich bibliotek Javy i innych języków stanowi witryna pod adresem http://www.xml-rpc.com. Serwis ten, sponsorowany przez Userland (http://www.userland.com), zawiera publiczną specy­fi­ka­cję XML-RPC, informacje o obsługiwanych typach danych i samouczki. Co ważniejsze jednak, można tam znaleźć odsyłacz do miejsca w sieci, z którego pobierzemy pakiet XML-RPC dla Javy, a mianowicie do strony Hannesa Wallnofera http://helma.at/hannes/xmlrpc.

Na stronie tej znajduje się opis klas zawartych w pakiecie XML-RPC Hannesa, a także instrukcja ich obsługi. Pobieramy archiwum i rozpakowujemy je w katalogu roboczym lub środowisku pro­gra­mistycznym IDE. Następnie kompilujemy klasy; jeden z przykładów serwletów wymaga obec­ności klas serwleta (servlet.jar dla interfejsu Servlet PI 2.2). Odpowiednią klasę dla mechanizmu serwletów Tomcat znaleźć można na stronie http://jakarta.apache.org. Jeśli Czytelnik nie za­mie­rza „bawić się” przykładem z serwletem, to nie musi go kompilować — nie jest on wymagany do wykonania przykładów w tym rozdziale.

0x01 graphic

Klasy XML-RPC są spakowane w pliku zip, xmlrpc-java.zip. Z tego archiwum trzeba wy­dobyć cały kod źródłowy znajdujący się w katalogu xmlrpc-java/src/. Nie do­łą­czo­no dystrybucji w postaci jar, więc wymagana jest ręczna kompilacja klas. Po skom­pilowaniu Czytelnik może sam utworzyć archiwum jar, co uprości włączanie klas do ścieżki. (W wersji pobranej przez tłumacza w styczniu 2001 r. odpowiedni plik jar był już dołączony — przyp. tłum.).

Zasadnicza dystrybucja (nie zawierająca przykładów apletów i wyrażeń regularnych) składa się z ośmiu klas pakietu helma.xmlrpc: klasa zasadnicza XmlRpc, klient XmlRpcClient, ser­wer XmlRpcServer, XmlRpcHandler (precyzyjne sterowanie kodowaniem i przetwarzaniem XML-a) oraz szereg klas pomocniczych. Klasy zgłaszają wyjątek XmlRpcException; nato­miast XmlRpcServlet demonstruje użycie serwleta jako procedury obsługi odpowiedzi HTTP; z kolei WebServer to „lekki” serwer HTTP zbudowany specjalnie do obsługi żądań XML-RPC; zaś Benchmark umożliwia zmierzenie czasu obsługi żądania XML-RPC z wykorzystaniem spe­cyficznego sterownika SAX. Wymagane do pracy całego mechanizmu, a nie zawarte w dystry­bu­cji, są klasy SAX (które Czytelnik powinien posiadać po wykonaniu wcześniejszych przykładów) oraz sterownik SAX; innymi słowy, trzeba mieć kompletną implementację parsera XML obsłu­gu­jącą SAX. W naszych przykładach będziemy ponownie korzystali z parsera Apache Xerces, ale biblioteki obsługują dowolny parser zgodny z SAX 1.0.

Po skompilowaniu plików źródłowych należy upewnić się, czy klasy XML-RPC, SAX i parsera XML znajdują się w ścieżce dostępu do klas. Po tym Czytelnik powinien już potrafić napisać pierwszy program. Warto trzymać gdzieś pod ręką pliki źródłowe XML-RPC, bo pozwalają one na podejrzenie, co dzieje się „pod maską” naszego przykładu.

Pisanie procedury obsługi

Po pierwsze, konieczne jest napisanie klasy i metody, która ma być uruchamiana zdalnie. Nazywa się ją zazwyczaj procedurą obsługi. Pamiętajmy jednak, że mechanizm XML-RPC obsługujący żą­dania często określany jest taką samą nazwą — znów daje się we znaki dwuznaczność przy­ję­tego nazewnictwa. Procedura obsługi XML_RPC to metoda lub metody pobierające żądanie XML-RPC, dekodujące jego zawartość i oddelegowujące to żądanie do jakiejś klasy lub metody. Procedura obsługi odpowiedzi, czy też po prostu procedura obsługi, to metoda wywoływana przez procedurę obsługi XML-RPC. Posiadając biblioteki XML-RPC dla Javy, nie musimy pisać procedury obsługi XML-RPC, ponieważ jest już ona zawarta w klasie helma.xmlrpc.Xml­Rpc­Server. Trzeba jedynie napisać klasę z jedną lub dwoma metodami, które zamierzamy za­re­jestrować w serwerze.

Niespodzianką jest fakt, że tworzenie procedury obsługi nie wymaga budowania podklas ani in­nych specjalnych zabiegów w kodzie. Poprzez XML-RPC można wywołać dowolną metodę, o ile tylko XML-RPC obsługuje jej parametry i zwracany typ danych (potrafi je zakodować). W tabeli 10.1 przedstawiono wszystkie obecnie obsługiwane typy Javy, jakie można wykorzystać w sy­gna­turach metod XML-RPC.

Lista zawiera tylko nieliczne typy, ale — jak się okazuje — umożliwiają one obsługę większości żądań XML-RPC wysyłanych poprzez sieć. Ponieważ my chcemy pobrać tylko jeden parametr String i również String zwrócić, w naszym programie powyższe typy zupełnie wystarczą. Zbudujmy więc teraz prostą klasę procedury obsługi (przykład 10.1).

Tabela 10.1. Typy Javy obsługiwane przez XML-RPC

Typ danych XML-RPC

Typ danych Javy

int

int

boolean

boolean

string

java.lang.String

double

double

dateTime.iso8601

java.util.Date

struct

java.util.Hashtable

array

java.util.Vector1

base64

byte[]

Przykład 10.1. Klasa procedury obsługi z metodą uruchamianą zdalnie

/**

* <b><code>HelloHandler</code></b> to prosta procedura obsługi

* rejestrowana w serwerze XML-RPC.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class HelloHandler {

/**

* <p>

* Pobieramy <code>String</code> i zwracamy komunikat

* powitalny wskazanemu użytkownikowi.

* </p>

*

* @param name <code>String</code> nazwa użytkownika.

* @return <code>String</code> - komunikat Witaj.

*/

public String sayHello(String name) {

return "Witaj " + name;

}

}

I to naprawdę tylko tyle. Metoda pobiera i zwraca dozwolone parametry XML-RPC, a więc mo­żemy spokojnie zarejestrować ją w serwerze XML-RPC — mamy gwarantowane, że będzie ją moż­na przez XML-RPC wywołać.

Budowanie serwera

Skoro procedura obsługi jest już gotowa, musimy napisać program uruchamiający serwer XML-RPC, nasłuchujący żądań i przekazujący je do procedury obsługi. W naszym przypadku w funkcji procedury obsługującej żądania wykorzystamy klasę helma.xmlrpc.WebServer. Można by­ło­by w tym celu użyć serwletu Javy, ale skorzystanie z „lekkiej” implementacji serwera pozwoli uniknąć konieczności uruchamiania mechanizmu serwletów. W zakończeniu rozdziału zostaną omó­wione serwlety w kontekście serwera XML-RPC. Wracając do naszego serwera — chcemy po­zwo­lić na określenie portu, na którym serwer zostanie uruchomiony, a następnie spowodować, aby serwer nasłuchiwał żądań XML-RPC dopóty, dopóki nie zostanie wyłączony. Następnie zareje­stru­jemy w tym serwerze stworzoną wcześniej klasę i określimy inne parametry specyficzne dla na­szej aplikacji.

Stwórzmy teraz szkielet tej klasy (przykład 10.2); konieczne jest zaimportowanie klasy Web­Ser­ver oraz pobranie portu z wiersza poleceń.

Przykład 10.2. Szkielet serwera XML-RPC „Witaj!”

import helma.xmlrpc.WebServer;

/**

* <b><code>HelloServer</code></b> to prosty serwer XML-RPC,

* który udostępni klasę <code>HelloHandler</code>

* wywołaniom XML-RPC.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class HelloServer {

/**

* <p>

* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi.

* </p>

*/

public static void main(String[] args) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloServer [port]");

System.exit(-1);

}

// Tu uruchomimy serwer na określonym porcie

}

}

Przed uruchomieniem serwera trzeba podać sterownik SAX, jaki zostanie wykorzystany do prze­tworzenia i kodowania XML. Domyślny sterownik SAX dla tych bibliotek to parser XP Jamesa Clarka (http://www.jclark.com). Jednak w naszym przykładzie zastosujemy parser Apache Xerces, określając w mechanizmie XML-RPC implementację SAX Parser. Czynność tę wykonuje się poprzez wywołanie statycznej metody setDriver(), należącej do klasy XmlRpc. Klasa ta leży u podstaw klasy WebServer, ale w celu użycia określonych sterowników SAX konieczne jest oddzielne jej zaimportowanie i bezpośrednie użycie. Metoda zgłasza wyjątek ClassNotFound­Ex­ception, a więc trzeba go przechwycić w celu określenia, czy klasa sterownika została od­naleziona. Wprowadźmy teraz opisywane zmiany:

import helma.xmlrpc.WebServer;

import helma.xmlrpc.XmlRpc;

...

/**

* <p>

* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi.

* </p>

*/

public static void main(String[] args) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloServer [port]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Uruchamiamy serwer

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

}

}

...

Teraz można już wstawić główny fragment kodu, nasłuchujący HTTP i obsługujący żądania XML-RPC, a następnie rejestrujący klasy procedur obsługi dostępne do wywoływania zdalnego. Tworzenie modułu nasłuchującego jest niezwykle proste; egzemplarz klasy pomocniczej Web­Ser­ver, o której mówiliśmy, można utworzyć poprzez podanie jej portu do nasłuchiwania — i już nasz serwer będzie obsługiwał żądania XML-RPC! Choć nie mamy jeszcze klas dostępnych do wy­wołania, to serwer XML-RPC jest już gotowy do pracy. Dodamy linijkę tworzącą i uru­cha­mia­jącą serwer oraz kod wyświetlający komunikat o stanie. Dodamy także kolejną dyrektywę import dla wyjątku java.io.IOException. Ponieważ serwer musi uruchamiać się na pewnym por­cie, wyjątek ten może zostać zgłoszony w przypadku niedostępności portu lub innych problemów z uruchomieniem serwera. Zmodyfikowany kod wygląda następująco:

import java.io.IOException;

import helma.xmlrpc.WebServer;

import helma.xmlrpc.XmlRpc;

...

* <p>

* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi.

* </p>

*/

public static void main(String[] args) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloServer [port]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Uruchamiamy serwer

System.out.println("Uruchamianie serwera XML-RPC...");

WebServer server = new WebServer(Integer.parseInt(args[0]));

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

} catch (IOException e) {

System.out.println("Uruchomienie serwera nie jest możliwe: " +

e.getMessage());

}

}

...

Możemy teraz skompilować i wypróbować powyższą klasę. Powinien zostać wyświetlony ko­munikat o stanie, a następnie program powinien zatrzymać się, oczekując na żądania. Teraz trzeba dodać klasę procedury obsługi przyjmującą żądania.

Jedną z najistotniejszych różnic pomiędzy technologiami RMI i RPC jest sposób udostępniania me­tod. W RMI zdalny interfejs posiada sygnatury metod odpowiadające wszystkim metodom zdal­nym. Jeśli metoda implementowana jest na klasie serwera, ale do zdalnego interfejsu nie do­dano pasującej sygnatury, nowa metoda nie będzie mogła zostać wywołana przez klienta RMI. Z całym procesem łączy się więc konieczność modyfikacji sporej ilości kodu i rekompilacji klas RMI. W RPC wszystko to wygląda inaczej — a sposób obsługi uznawany jest za elastyczniejszy i prostszy. Serwer RPC otrzymuje żądanie zawierające parametry i wartość tekstową (zazwyczaj w po­staci „nazwaklasy.nazwametody”). Serwer RPC „widzi”, że metoda znajduje się w klasie „naz­waklasy” i nosi nazwę „nazwametody”. Próbuje więc odnaleźć pasującą klasę i metodę po­bie­ra­ją­cą parametr takiego typu, jaki jest w żądaniu RPC. Po znalezieniu określona metoda zostaje wy­wołana, a rezultat tego wywołania jest kodowany i odsyłany klientowi.

Całe to nieco zawiłe wyjaśnienie sprowadza się do jednego — żądana metoda nigdy nie jest jaw­nie definiowana w serwerze XML-RPC, a jedynie w żądaniu otrzymanym od klienta. W serwerze rejes­tro­wany jest jedynie egzemplarz klasy. Do klasy tej można dodawać metody, a ich udo­stęp­nie­nie wymaga jedynie ponownego uruchomienia serwera — a nie zmian w kodzie. O ile tylko po­trafimy określić i wysłać poprawne parametry do serwera, to metoda taka jest od razu dostępna. To właśnie jedna z zalet XML-RPC względem RMI — interfejs API jest reprezentowany w sposób bliższy rzeczywistości; w kliencie nie trzeba aktualizować żadnych procedur pośredniczących, szkie­letów czy interfejsów. Po dodaniu metody jej sygnatura może zostać opublikowana klientom i natychmiast wykorzystana.

Skoro wiemy już, w jak prosty sposób wykorzystywana jest procedura obsługi RPC, zarejestrujmy taką procedurę w naszym przykładzie. Klasa WebServer umożliwia dodanie procedury poprzez metodę addHandler(). Metoda ta jako dane wejściowe pobiera nazwę, pod jaką procedura ob­sługi zostanie zarejestrowana, oraz sam egzemplarz klasy tej procedury. Zazwyczaj tworzy się w tym celu egzemplarz nowej klasy za pomocą jej konstruktora (poprzez słowo kluczowe new), choć w dalszej części Czytelnik zobaczy, w jaki sposób można zrobić to inaczej (gdy egzemplarz ma być współdzielony przez wiele klientów, a nie tworzony dla każdego oddzielnie). Ale w po­niższym przykładzie stworzenie egzemplarza nowej klasy jest dobrym rozwiązaniem. Zare­je­stru­jemy klasę HelloHandler pod nazwą „hello”. Dodamy także instrukcje wyświetlające dalsze komunikaty, tak by możliwe było „podejrzenie”, co dzieje się w serwerze w czasie dodawania procedury obsługi:

import java.io.IOException;

import helma.xmlrpc.WebServer;

import helma.xmlrpc.XmlRpc;

/**

* <b><code>HelloServer</code></b> to prosty serwer XML-RPC,

* który udostępni klasę <code>HelloHandler</code>

* wywołaniom XML-RPC.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class HelloServer {

/**

* <p>

* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi.

* </p>

*/

public static void main(String[] args) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloServer [port]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Uruchamiamy serwer

System.out.println("Uruchamianie serwera XML-RPC...");

WebServer server = new WebServer(Integer.parseInt(args[0]));

// Rejestrujemy klasę procedury obsługi

server.addHandler("hello", new HelloHandler());

System.out.println(

"Klasa procedury obsługi zarejestrowana jako \"hello\"");

System.out.println("Teraz czekamy na żądania...");

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

} catch (IOException e) {

System.out.println("Uruchomienie serwera nie jest możliwe: " +

e.getMessage());

}

}

}

Po skompilowaniu powyższego kodu źródłowego można uruchomić serwer. Wynik powinien być po­dobny do tego z przykładu 10.3.

Przykład 10.3. Uruchamianie klasy serwera XML-RPC HelloServer

[adam@maly ch10]$ java HelloServer 8585

Uruchamianie serwera XML-RPC...

Klasa procedury obsługi zarejestrowana jako "hello"

Teraz czekamy na żądania...

Tak, to właśnie jest takie proste! Teraz możemy utworzyć klienta i „bawić się” komunikacją w sieci poprzez XML-RPC. To właśnie kolejna zaleta XML-RPC — niewiele potrzeba, aby taki mechanizm uruchomić, szczególnie jeśli porównamy to rozwiązanie z technologią RMI. W trakcie lektury kolejnego podrozdziału Czytelnik przekona się, że utworzenie klienta jest równie łatwe.

Budowanie klienta

Skoro serwer działa i przyjmuje żądania, to mamy już za sobą najtrudniejszy etap programowania aplikacji XML-RPC (tak, to była ta trudniejsza część!). Teraz zbudujemy prostego klienta wy­wołującego zdalnie naszą metodę sayHello(). Korzystamy w tym celu z klasy helma.xml­rpc.XmlRpcClient. Odpowiedzialna jest ona za wiele czynności odbywających się po stronie klienta; jej odpowiednikami po stronie klienta są XmlRpcServer i WebServer. Aby utworzyć klienta, oprócz wspomnianej klasy będziemy jeszcze potrzebowali klasy XmlRpc — klient musi umieć zakodować żądanie, a więc znów musimy ustawić klasę sterownika SAX do wykorzystania w metodzie setDriver(). Zacznijmy tworzenie klienta właśnie od tych ważnych instrukcji, spraw­dzając argument przekazywany do metody sayHello() na serwerze oraz wykonując pod­stawową obsługę wyjątków. Tworzymy plik źródłowy Javy HelloClient.java — taki jak w przy­kładzie 10.4.

Przykład 10.4. Klient XML-RPC

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.XmlRpcClient;

/**

* <b><code>HelloClient</code></b> to prosty klient XML-RPC

* wysyłający żądanie do <code>HelloServer</code>.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class HelloClient {

/**

* <p>

* Łączymy się z serwerem XML-RPC i wysyłamy żądanie.

* </p>

*/

public static void main(String args[]) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloClient [twoje imie]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Określamy serwer

// Tworzymy żądanie

// Wysyłamy żądanie i drukujemy wynik

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

}

}

}

Tak jak cały kod w tym rozdziale, powyższy przykład nie powinien sprawiać Czytelnikowi żad­nych trudności. Zbudowanie klienta XML-RPC wymaga najpierw stworzenia klasy XmlRpcClient, której dostarcza się nazwę serwera XML-RPC. Nazwa tego serwera powinna mieć postać pełnego adresu URL, łącznie z przedrostkiem http://. Jeśli adres ten ma nieodpowiedni format, to w czasie tworzenia klienta zostanie zgłoszony wyjątek java.net.MalformedURLException. Dodajmy wspomnianą klasę do listy klas importowanych, stwórzmy egzemplarz naszego klienta i dodajmy wymaganą procedurę obsługi:

import java.io.IOException;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.XmlRpcClient;

/**

* <b><code>HelloClient</code></b> to prosty klient XML-RPC

* wysyłający żądanie do <code>HelloServer</code>.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class HelloClient {

/**

* <p>

* Łączymy się z serwerem XML-RPC i wysyłamy żądanie.

* </p>

*/

public static void main(String args[]) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloClient [twoje imie]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Określamy serwer

XmlRpcClient client =

new XmlRpcClient("http://localhost:8585/");

// Tworzymy żądanie

// Wysyłamy żądanie i drukujemy wynik

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

} catch (MalformedURLException e) {

System.out.println(

"Niepoprawny format URL serwera XML-RPC: " +

e.getMessage());

}

}

...

Choć faktyczne żądania RPC nie są jeszcze wysyłane, to jest to już w pełni funkcjonalna aplikacja klienta. Można ją skompilować i uruchomić, choć nie dostrzeżemy żadnych objawów jej działania — przecież aż do wysłania żądania nie następuje komunikacja pomiędzy obydwoma kom­po­nen­tami naszego rozwiązania.

0x01 graphic

Oczywiście, w kodzie źródłowym należy wpisać numer portu, na jakim zamierzamy uru­chomić serwer. Rzecz jasna, nie jest to rewelacyjny sposób uzyskiwania ko­mu­ni­kacji pomiędzy klientem a serwerem; zmiana portu, na którym nasłuchuje serwer, wy­maga zmiany kodu klienta! W następnym rozdziale przedstawiony zostanie sposób rozwiązania tego problemu.

Widać teraz, jak proste jest tworzenie serwera i klienta oraz jak niewiele potrzeba, aby uruchomić mechanizm XML-RPC.

Jednak z naszego programu nie będzie dużego pożytku, dopóki nie zdefiniujemy, jak ma wysyłać żą­­dania i otrzymywać odpowiedzi. Zakodowanie żądania wymaga wywołania na egzemplarzu Xml­RpcClient metody execute(). Pobiera ona dwa parametry: nazwę identyfikatora klasy oraz metodę do wywołania (mające postać pojedynczego parametru String) oraz Vector parametrów przekazywanych do określonej metody. Identyfikator klasy to nazwa, jaką zarejestrowaliśmy dla klasy HelloHandler na serwerze XML-RPC. Identyfikator ten może być po prostu nazwą kla­sy, ale zazwyczaj korzysta się z bardziej czytelnej i zrozumiałej dla klienta nazwy — tak jak u nas „hello”. Dalej dołączana jest nazwa wywoływanej metody, oddzielona od identyfikatora klasy kropką — [identyfikator klasy].[nazwa metody]. Parametry muszą mieć postać we­ktora Vector Javy i powinny zawierać wszystkie obiekty parametrów wymagane przez okreś­lo­ną metodę. W naszej prostej metodzie sayHello() jest to String zawierający nazwę użyt­kownika, którą podajemy w wierszu poleceń.

Kiedy już klient XML-RPC zakoduje żądanie, wysyła je do serwera. Serwer odnajduje klasę pa­sującą do podanego identyfikatora i szuka odpowiedniej metody. Jeśli zostanie ona znaleziona, przyj­mowane przez nią parametry porównywane są z tymi z żądania. Jeśli parametry się zgadzają, następuje wykonanie metody. W przypadku znalezienia wielu metod o tej samej nazwie, metodę do wywołania rozpoznaje się po parametrach; jest to zwykły proces w Javie, określany mianem prze­ładowania (ang. overloading). Wynik działania metody kodowany jest przez serwer XML-RPC i odsyłany klientowi jako Object Javy (który z kolei może być wektorem obiektów!). Wynik ten może być potem rzutowany na odpowiedni typ Javy i zwyczajnie wykorzystywany w kliencie. Jeśli nie zostanie znaleziona pasująca sygnatura identyfikatora lub metody lub parametrów, klien­towi zgłasza się wyjątek XmlRpcException. To gwarantuje, że klient nie będzie próbował wy­woływać nieistniejących metod lub metod istniejących, ale z niewłaściwymi parametrami.

Cała powyższa procedura zawarta jest w zaledwie kilku wierszach kodu Javy. Przede wszystkim konieczne jest zaimportowanie klasy XmlRpcException oraz java.io.IOException; ten ostatni wyjątek zgłaszany jest w przypadku nieprawidłowej komunikacji pomiędzy klientem a ser­werem. Następnie dodajemy klasę Vector i tworzymy jej egzemplarz, podając mu jeden para­metr String. To umożliwi wywołanie metody execute() z nazwą procedury obsługi, metodą
do wywołania i jej parametrami; wynik jej działania rzutowany jest na String, ten zaś zostaje wyświetlony na ekranie. W niniejszym przykładzie zakładamy, że serwer XML-RPC działa na komputerze lokalnym, na porcie 8585:

import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Vector;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.XmlRpcClient;

import helma.xmlrpc.XmlRpcException;

...

/**

* <p>

* Łączymy się z serwerem XML-RPC i wysyłamy żądanie.

* </p>

*/

public static void main(String args[]) {

if (args.length < 1) {

System.out.println(

"Użycie: java HelloClient [twoje imie]");

System.exit(-1);

}

try {

// Korzystamy ze sterownika Apache Xerces SAX

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Określamy serwer

XmlRpcClient client =

new XmlRpcClient("http://localhost:8585/");

// Tworzymy żądanie

Vector params = new Vector();

params.addElement(args[0]);

// Wysyłamy żądanie i wyświetlamy wynik.

String result =

(String)client.execute("hello.sayHello", params);

System.out.println("Odpowiedź serwera: " + result);

} catch (ClassNotFoundException e) {

System.out.println("Nie odnaleziono sterownika SAX");

} catch (MalformedURLException e) {

System.out.println(

"Niepoprawny format URL serwera XML-RPC: " +

e.getMessage());

} catch (XmlRpcException e) {

System.out.println("Wyjątek XML-RPC: " + e.getMessage());

} catch (IOException e) {

System.out.println("Wyjątek IO: " + e.getMessage());

}

}

...

To już wszystko! Przykład kompilujemy i uruchamiamy zgodnie z poniższym opisem.

Mów do mnie...

Należy sprawdzić, czy w ścieżce dostępu do klas znajdują się klasy XML-RPC i klasy opisy­wa­nych wyżej przykładów. Musi się tam znaleźć również Apache Xerces lub inny sterownik SAX
—z jego pomocą programy wykonują przetwarzanie. Kiedy klasy są już dostępne, uruchamiamy serwer z odpowiednim numerem portu. W systemie Windows, aby uruchomić serwer jako od­dziel­ny proces, korzystamy z polecenia start:

D:\prod\Java and XML\WEB-INF\classes>start java HelloServer 8585

Uruchamianie serwera XML-RPC...

Klasa procedury obsługi zarejestrowana jako "hello"

Teraz czekamy na żądania...

W systemach Unix uruchamiamy proces w tle (wstawiając na końcu &), co umożliwi nam uru­chomienie na tej samej konsoli również klienta; ewentualnie klienta można uruchomić na innej konsoli, kopiując wcześniej środowisko uruchomieniowe:

$ java HelloServer 8585 &

Uruchamianie serwera XML-RPC...

Klasa procedury obsługi zarejestrowana jako "hello"

Teraz czekamy na żądania...

Teraz uruchamiamy klienta, jako argument wywołania podając własne imię. Wkrótce pojawi się od­powiedź podobna do tej w przykładzie 10.5 — serwer HelloServer otrzymał i obsłużył żą­danie oraz zwrócił wynik działania metody sayHello():

Przykład 10.5. Działanie klasy HelloClient

$ java HelloClient Brett

Odpowiedź serwera: Witaj Brett

I tak właśnie działa XML-RPC. Oczywiście, aplikacja taka jak powyższa nie jest zbyt przydatna, ale na pewno dobrze ilustruje podstawy działania i łatwość budowania serwera i klienta XML-RPC w Javie. Po takim wprowadzeniu możemy przejść do bardziej realistycznego przykładu. W dalszej części rozdziału zbudujemy praktyczniejszy i pożyteczniejszy serwer; Czytelnik będzie mógł rów­nież zobaczyć, jak wyglądają procedury obsługi XML-RPC. Następnie stworzymy klienta (podob­ne­go do HelloClient) i przetestujemy działanie nowego kodu.

Po co się męczyć? Niech serwer nas wyręczy!

Opisany wyżej przykład Hello dobrze demonstruje sposób korzystania z XML-RPC w Javie, ale nie jest zbyt realistyczny. Kod jest banalny, serwer mało elastyczny, a sama procedura obsługi nie ukazuje praktycznego zastosowania technologii XML-RPC. Poniżej przedstawione zostanie za­stosowanie technologii XML-RPC w środowisku produkcyjnym — zbudujemy bardziej uży­te­cz­ną procedurę obsługi i bardziej praktyczny serwer. Kod taki, choć może wciąż jeszcze nie­przy­datny w „prawdziwym” projekcie, na pewno zilustruje, do czego Czytelnik mógłby wykorzystać model XML-RPC w swojej przyszłej pracy.

Współużytkowana procedura obsługi

Utworzona wcześniej klasa HelloHandler była prosta i... bezużyteczna. Trzeba pamiętać, że z technologii XML-RPC w dużej mierze korzysta się po to, aby zrzucić ciężar wykonywania bar­dziej złożonych zadań na serwer — uproszczony klient ma tylko żądać wywołania procedury i ko­rzystać ze zwróconych wyników. Co więcej, możliwe jest, że część, a nawet wszystkie obliczenia wymagane do zwrócenia odpowiedzi na żądanie można wykonać zawczasu; inaczej mówiąc, klasa procedury obsługi sama wykonuje zadania i kiedy nadchodzi wywołanie metody, wyniki są już gotowe do odesłania. W tej chwili programistom Javy powinny przyjść na myśl wątki i współ­użyt­ko­wane egzemplarze danych. Właśnie te zagadnienia zostaną poniżej zilustrowane za pomocą prostej klasy Scheduler (rozkład zadań).

Do stworzonego rozkładu zadań klienty będą mogły dodawać i usuwać zdarzenia. Będzie również możliwość pobrania z rozkładu listy wszystkich zdarzeń w kolejce. Aby aplikację jeszcze bardziej zbliżyć do rzeczywistości (i żeby serwer miał co robić), nakażemy sortowanie zwróconych zadań według kolejności ich wykonywania. Zdarzenia będą miały po prostu postać łańcucha String, za­wierającego nazwę i czas zdarzenia w formacie java.util.Date. Nie jest to może kom­ple­tna implementacja rozkładu zadań, ale dobrze demonstruje działanie serwera, wykonuje pewne za­dania „za kulisami”.

Po pierwsze, stworzymy metody addEvent() i removeEvent() (odpowiednio: dodaj i usuń zdarzenie). Ponieważ obie te metody są wywoływane przez klienta, nie ma tutaj nic nowego; cie­ka­we jest natomiast to, jak zachowamy te zadania w klasie Scheduler. Serwer XML-RPC stwo­rzy egzemplarz tej klasy i egzemplarz ten będzie potem wykorzystywany we wszystkich żądaniach przychodzących do serwera; jest jednak możliwe, a nawet prawdopodobne, że z naszym roz­kła­dem zajęć będą komunikowały się inne klasy lub wręcz inne serwery XML-RPC. Jeśli listę zda­rzeń będziemy składować jako zmienną przynależną do klasy, nie będzie możliwe współ­użyt­ko­wa­nie tych danych przez wiele egzemplarzy. Trzeba więc stworzyć dane składowane statycznie i współ­użytkowane przez wszystkie egzemplarze klasy Scheduler. Do przechowania nazwy i czasu zda­rze­nia najbardziej odpowiedni wydaje się typ Hashtable, w którym dane przechowywane są w parach klucz-wartość. Oprócz tego nazwy zdarzeń zachowamy w wektorze Vector. Wymaga to wykorzystania dodatkowej pamięci masowej (oraz pamięci w wirtualnej maszynie Javy), ale za to sortować będziemy Vector, a nie Hashtable — zaleta takiego rozwiązania polega na tym, że będziemy zamieniać miejscami pozycje wektora (za każdym razem tylko pojedyncza zamiana), a nie Hashtable (każdorazowo dwie zamiany). Stwórzmy szkielet takiej klasy i dodajmy dwie pierwsze metody, pozwalające dodać lub usunąć zdarzenie. Dodamy także miejsce składowania zda­rzeń, ale implementację pobierania i sortowania zdarzeń zostawimy na później. Odpowiedni kod można obejrzeć w przykładzie 10.6.

Przykład 10.6. Klasa rozkładu zadań Scheduler

import java.util.Date;

import java.util.Hashtable;

import java.util.Vector;

/**

* <b><code>Scheduler</code></b> to klasa umożliwiająca

* dodawanie, usuwanie i pobieranie posortowanych według czasu

* występowania zadań.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class Scheduler {

/** Lista nazw zdarzeń (do sortowania) */

private static Vector events = null;

/** Szczegóły zdarzeń (nazwa, czas) */

private static Hashtable eventDetails = null;

/**

* <p>

* Tutaj inicjalizujemy miejsce składowania zdarzeń.

* </p>

*/

public Scheduler() {

events = new Vector();

eventDetails = new Hashtable();

}

/**

* <p>

* Tutaj dodajemy żądane zdarzenie.

* </p>

*

* @param eventName <code>String</code> nazwa zdarzenia do dodania.

* @param eventTime <code>Date</code> data zdarzenia.

* @return <code>boolean</code> - wskazuje, czy zdarzenie zostało dodane.

*/

public boolean addEvent(String eventName, Date eventTime) {

// Dodajemy tę listę do listy zdarzeń

if (!events.contains(eventName)) {

events.addElement(eventName);

eventDetails.put(eventName, eventTime);

}

return true;

}

/**

* <p>

* Tutaj usuwamy żądane zdarzenie.

* </p>

*

* @param eventName <code>String</code> nazwa zdarzenia do usunięcia.

* @return <code>boolean</code> - wskazuje, czy usunięto zdarzenie.

*/

public synchronized boolean removeEvent(String eventName) {

events.remove(eventName);

eventDetails.remove(eventName);

return true;

}

}

Metoda addEvent() dodaje nazwę zdarzenia do obu obiektów, w których składujemy dane, a czas zdarzenia — tylko do obiektu Hashtable. Metoda removeEvent() ma działanie przeciwne. Obie zwracają wartość boolean. Choć w przykładzie ta wartość to zawsze true, w bardziej zło­żo­nych aplikacjach można ją wykorzystać do informowania o problemach związanych z doda­wa­niem lub usuwaniem zdarzeń.

Skoro dodawanie i usuwanie zdarzeń jest już możliwe, teraz musimy dodać metodę zwracającą listę zdarzeń. Metoda ta zwraca wszystkie składowane zdarzenia, bez względu na to, który klient (lub aplikacja) to zdarzenie dodał. Mogą więc to być zdarzenia dodane przez innego klienta XML-RPC, inny serwer XML-RPC, inną aplikację albo samodzielną implementację tego samego roz­kła­du zadań. Ponieważ zwracane dane to pojedynczy Object, można zwrócić Vector sfor­ma­to­wa­nych wartości String, zawierających nazwę i czas każdego zdarzenia. Oczywiście, w aplikacji bliższej rzeczywistości mógłby zostać zwrócony Vector zdarzeń albo jakiś inny typ danych (z da­tą w obiekcie Date itd.). Nasza metoda ma jednak umożliwić podgląd danych, a nie wykonywanie na nich dalszych operacji. Do zwrócenia listy zdarzeń wykorzystamy klasę java.text.Si­m­pleDateFormat, umożliwiającą tekstowe formatowanie obiektów Date. Odpowiedni String, zawierający nazwę i czas zdarzenia, tworzony jest w pętli dla każdego zdarzenia; potem dołączany jest do listy wynikowej Vector, która to lista zostanie zwrócona klientowi. Dodajmy więc wy­ma­ganą instrukcję importującą oraz kod zwracający zdarzenia:

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Hashtable;

import java.util.Vector;

...

/**

* <p>

* Tutaj zwracamy bieżącą listę zdarzeń.

* </p>

*

* @return <code>Vector</code> - lista zdarzeń.

*/

public Vector getListOfEvents() {

Vector list = new Vector();

// Tworzymy format danych.

SimpleDateFormat fmt =

new SimpleDateFormat("hh:mm a MM/dd/yyyy");

// Dodajemy każde zdarzenie do listy

for (int i=0; i<events.size(); i++) {

String eventName = (String)events.elementAt(i);

list.addElement("Zdarzenie \"" + eventName +

"\" zaplanowane na " +

fmt.format(

(Date)eventDetails.get(eventName)));

}

return list;

}

...

Teraz można byłoby już bez żadnych problemów użyć powyższej klasy jako procedury obsługi XML-RPC. Jednak chcemy dowiedzieć się, co należy zrobić, aby serwer wykonywał swoją pracę w czasie, gdy klient robi coś innego. Metoda getListOfEvents() zakłada, że lista zdarzeń (zmienna events typu Vector) jest posortowana — czyli że sortowanie miało już wcześniej miejsce. Nie napisaliśmy jeszcze kodu sortującego, ale — co istotniejsze — nie napisaliśmy rów­nież kodu uruchamiającego to sortowanie. Co więcej, w miarę rozrastania się listy zdarzeń sor­to­wa­nie będzie zajmowało coraz więcej czasu, a przecież klient nie może czekać. Najpierw dodamy metodę, za pomocą której nasza klasa posortuje zdarzenia. Dla uproszczenia zastosujemy technikę sortowania pęcherzykowego; omawianie algorytmów sortowania wykracza poza tematykę niniej­szej książki, a więc nie będziemy szczegółowo opisywali akurat tego fragmentu kodu. W każdym razie na końcu metody zmienna Vector events zawiera już zdarzenia posortowane według za­planowanych dat. Więcej informacji o tym i o innych algorytmach sortowania można znaleźć w książce Algorithms in Java Roberta Sedgewicka i Tima Lindholma (Addison-Wesley). W na­stę­pujący sposób dodamy algorytm i metodę sortującą zdarzenia:

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Enumeration;

import java.util.Hashtable;

import java.util.Vector;

/**

* <b><code>Scheduler</code></b> to klasa umożliwiająca

* dodawanie, usuwanie i pobieranie posortowanych według czasu

* występowania zadań.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class Scheduler {

/** Lista nazw zdarzeń (do sortowania) */

private static Vector events = null;

/** Szczegóły zdarzeń (nazwa, czas) */

private static Hashtable eventDetails = null;

/** Znacznik określający, czy zdarzenia są posortowane */

private static boolean eventsSorted;

// Implementacje innych metod

/**

* <p>

* Sortujemy zdarzenia na bieżącej liście.

* <p>

*/

private synchronized void sortEvents() {

if (eventsSorted) {

return;

}

// Tworzymy tablicę zdarzeń "tak jak są" (nieposortowanych)

String[] eventNames = new String[events.size()];

events.copyInto(eventNames);

// Sortowanie pęcherzykowe

String tmpName;

Date date1, date2;

for (int i=0; i<eventNames.length - 1; i++) {

for (int j=0; j<eventNames.length - i - 1; j++) {

// Porównanie dat zdarzeń

date1 = (Date)eventDetails.get(eventNames[j]);

date2 = (Date)eventDetails.get(eventNames[j+1]);

if (date1.compareTo(date2) > 0) {

// W razie potrzeby -- zamiana zdarzeń

tmpName = eventNames[j];

eventNames[j] = eventNames[j+1];

eventNames[j+1] = tmpName;

}

}

}

// Wstawienie posortowanych zdarzeń do nowego wektora

Vector sortedEvents = new Vector();

for (int i=0; i<eventNames.length; i++) {

sortedEvents.addElement(eventNames[i]);

}

// Aktualizacja zdarzeń globalnych

events = sortedEvents;

eventsSorted = true;

}

...

}

Poza wstawieniem samego algorytmu trzeba było jeszcze zaimportować klasę java.util.Enu­meration oraz dodać zmienną boolean o nazwie eventsSorted. Znacznik ten umożliwia obejście całej procedury sortowania, jeśli zdarzenia są już posortowane. Nie ma jeszcze kodu, który wpływałby na stan tego znacznika, ale jego dodanie nie powinno sprawić kłopotów. Nasza metoda sortująca już teraz potrafi wskazać, czy po ukończeniu zdarzenia są posortowane. Kon­struktor powinien więc na początku ustawić tę wartość na true, co oznacza, że zdarzenia są wstęp­nie posortowane. Dopiero po dodaniu nowych zdarzeń ten stan może się zmienić na „nie­posortowane”, a więc w metodzie addEvents() ustawiamy znacznik na false. Dzięki temu klasa Scheduler będzie „wiedzieć”, że sortowanie powinno zostać uruchomione. Kiedy więc wywołana zostanie metoda getListOfEvents(), zdarzenia będą posortowane i gotowe do zwrócenia. Dodajmy odpowiedni kod do naszego konstruktora:

/**

* <p>

* Tutaj inicjalizujemy miejsce składowania zdarzeń.

* </p>

*/

public Scheduler() {

events = new Vector();

eventDetails = new Hashtable();

eventsSorted = true;

}

/**

* <p>

* Tutaj dodajemy żądane zdarzenie.

* </p>

*

* @param eventName <code>String</code> nazwa zdarzenia do dodania.

* @param eventTime <code>Date</code> data zdarzenia.

* @return <code>boolean</code> - wskazuje, czy zdarzenie zostało dodane.

*/

public boolean addEvent(String eventName, Date eventTime) {

// Dodajemy tę listę do listy zdarzeń

if (!events.contains(eventName)) {

events.addElement(eventName);

eventDetails.put(eventName, eventTime);

eventsSorted = false;

}

return true;

}

W metodzie removeEvent() nie trzeba wprowadzać żadnych zmian — usunięcie wpisu nie zmienia przecież kolejności zdarzeń. Idealnym rozwiązaniem przetwarzania po stronie serwera bez angażowania klienta jest stworzenie wątku sortującego. Po uruchomieniu takiego wątku w wir­tualnej maszynie Javy przetwarzanie po stronie klienta może być kontynuowane bez konieczności czekania na zakończenie procesu po stronie serwera. Jest to szczególnie istotne w środowiskach wielowątkowych, gdzie wykorzystuje się synchronizację i wątki oczekujące na zwolnienie blokad obiektów. W tym przykładzie staramy się uniknąć tego typu zagadnień (to rozdział o XML-RPC, a nie o wątkach), ale dodanie odpowiedniego kodu nie stanowiłoby dużego problemu. W naszym pro­gramie stworzymy wewnętrzną klasę Thread, która nie robi nic poza wywołaniem metody sortEvents(). Następnie dodamy metodę addEvents(), która tworzy i uruchamia ten wątek po dodaniu zdarzenia. Tak więc dodanie zdarzenia powoduje ponowne posortowanie zda­rzeń, ale bez zatrzymywania pracy klienta (czyli np. dodawania kolejnych zdarzeń, a więc uru­cha­miania kolejnych wątków itd.). Kiedy klient zażąda listy zdarzeń, powinny one być już posor­to­wane, tak aby klient nie musiał oczekiwać albo wykonywać tej czynności samodzielnie. Dodanie wewnętrznej klasy sortującej oraz kodu uruchamiającego tę klasę w wątku do metody add­Events() to już ostatnie zmiany, jakie wprowadzimy w klasie Scheduler:

public class Scheduler {

...

public boolean addEvent(String eventName, Date eventTime) {

// Dodajemy tę listę do listy zdarzeń

if (!events.contains(eventName)) {

events.addElement(eventName);

eventDetails.put(eventName, eventTime);

eventsSorted = false;

// Uruchamiamy na serwerze wątek sortujący

SortEventsThread sorter = new SortEventsThread();

sorter.start();

}

return true;

}

...

/**

* <p>

* Klasa wewnętrzna uruchamiająca sortowanie jako wątek

* <code>Thread</code>.

*/

class SortEventsThread extends Thread {

/**

* <p>

* Uruchamianie sortowania.

* </p>

*/

public void run() {

sortEvents();

}

}

}

Po skompilowaniu zmodyfikowanego kodu powstaje wątkowy rozkład zadań, wykonujący „zaso­bożerne” zadanie sortowania po stronie serwera, nie przerywając w tym celu pracy klientów. Jest

to prosty przykład klasy procedury obsługi, ale dobrze ilustruje proces rozpraszania zasobów i spo­sób przekładania ciężaru obsługi programu na serwer. Bardziej zaawansowana klasa procedury obsługi jest już gotowa. Teraz jeszcze usprawnimy nieco sam serwer.

Serwer z możliwością konfiguracji

Klasa serwera XML-RPC wymaga zmian. Obecna wersja wymaga wpisywania klas obsługi w kod — a więc również rekompilacji. To rozwiązanie niepożądane, niewygodne i czasochłonne — ko­nieczne jest uzyskiwanie najświeższego kodu z systemu kontroli plików źródłowych, wprowa­dza­nie zmian i testowanie procedur obsługi. Lepiej byłoby, gdyby serwer potrafił odczytywać tego typu informacje z pliku konfiguracyjnego i ładować odpowiednie klasy dynamicznie. Spróbujmy teraz stworzyć taki „lekki” serwer.

Zaczniemy od zbudowania nowej klasy serwera. Możemy zacząć od podstaw albo skopiować kod z klasy HelloServer opisywanej we wcześniejszej części rozdziału. Rozpoczniemy od stwo­rze­nia struktury i po kolei dodamy wymagane instrukcje importujące i inne — podobnie jak robi­liś­my to wcześniej. Tym razem jednak nie dodamy kodu rejestrującego procedury obsługi; posłużymy się metodą pomocniczą, ładującą wymagane informacje z pliku. Będzie więc trzeba również ob­służyć dodatkowy parametr wiersza poleceń, określający nazwę pliku. Stworzymy klasę Light­weightXmlRpcServer (część pakietu narzędziowego com.oreilly.xml), która cały czas korzysta z niewielkiej klasy pomocniczej WebServer. Jej kod przedstawiony jest w przykładzie 10.7. Pełny pakiet com.oreilly.xml można również pobrać ze stron http://www.orei­lly.com/ catalog/javaxml/ lub http://www.newInstance.com.

Przykład 10.7. Klasa LightweightXmlRpcServer

package com.oreilly.xml;

import java.io.IOException;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.WebServer;

/**

* <b><code>LightweightXmlRpcServer</code></b> to klasa narzędziowa

* uruchamiająca serwer XML-RPC nasłuchujący żądań HTTP

* i rejestrująca procedury obsługi zdefiniowane w pliku konfiguracyjnym.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class LightweightXmlRpcServer {

/** Klasa narzędziowa serwera XML-RPC */

private WebServer server;

/** Port, na którym nasłuchujemy */

private int port;

/** Plik konfiguracyjny */

private String configFile;

/**

* <p>

* Tutaj będziemy przechowywać nr portu i plik konfiguracyjny

* wykorzystywane przez serwer.

* </p>

*

* @param port <code>int</code> port, na którym nasłuchujemy

* @param configFile <code>String</code> nazwa pliku do

* pobrania informacji o konfiguracji.

*/

public LightweightXmlRpcServer(int port, String configFile) {

this.port = port;

this.configFile = configFile;

}

/**

* <p>

* Uruchomienie serwera.

* </p>

*

* @throws <code>IOException</code> zgłaszany w razie problemów.

*/

public void start() throws IOException {

try {

// Korzystamy z parsera SAX Apache Xerces

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

System.out.println("Uruchamianie serwera XML-RPC...");

server = new WebServer(port);

// Rejestracja procedur obsługi.

} catch (ClassNotFoundException e) {

throw new IOException("Błąd ładowania parsera SAX: " +

e.getMessage());

}

}

/**

* <p>

* Statyczny punkt rozpoczęcia programu.

* </p>

*/

public static void main(String[] args) {

if (args.length < 2) {

System.out.println(

"Użycie: " +

"java com.oreilly.xml.LightweightXmlRpcServer " +

"[port] [plikKonfiguracyjny]");

System.exit(-1);

}

LightweightXmlRpcServer server =

new LightweightXmlRpcServer(Integer.parseInt(args[0]),

args[1]);

try {

// Uruchomienie serwera.

server.start();

} catch (IOException e) {

System.out.println(e.getMessage());

}

}

}

W powyższym kodzie nie ma nic nadzwyczajnego. Sprawdzamy, czy przekazywane są odpo­wie­dnie parametry, i uruchamiamy serwer na żądanym porcie. Teraz trzeba dodać metody ładujące pro­cedury obsługi z pliku, a następnie dodające procedury jedna po drugiej do serwera.

Ponieważ każda procedura obsługi ma postać nazwy i odpowiadającej jej klasy, plik konfi­gu­ra­cyj­ny oparty będzie właśnie na takich parach informacji. W przypadku Javy załadowanie i stworzenie egzemplarza klasy, której pełna nazwa jest nam znana, nie przedstawia żadnych trudności — tak więc nową procedurę obsługi opiszemy za pomocą prostej pary łańcuchów tekstowych. W pliku kon­fi­gu­ra­cyjnym dodamy zarówno naszą wcześniejszą klasę HelloHandler, jak i nową — Sche­du­ler. Po­nie­waż piszemy również parser pliku, decydujemy, że ogranicznikami wpisów będą przecinki, a znak <CODE># oznaczać będzie komentarz. Możliwe jest zresztą zastosowanie do­wolnej konwencji, o ile tylko dostosujemy odpowiednio kod. Tworzymy teraz plik konfiguracyjny taki jak ten w przykła­dzie 10.8. W wyniku jego odczytania do serwera zostaną dodane klasy Hel­loHandler (pod nazwą „hello”) oraz Scheduler (pod nazwą „scheduler”). Plik zachowujemy pod nazwą xmlrpc.conf.

Przykład 10.8. Plik konfiguracyjny XML-RPC

# Procedura obsługi hello: sayHello()

hello,HelloHandler

# Scheduler: addEvent(), removeEvent(), getEvents()

scheduler,Scheduler

Na potrzeby dokumentacji w komentarzach podajemy metody dostępne dla każdej procedury ob­sługi. Osobom zajmującym się naszym kodem w przyszłości taki opis na pewno się przyda.

Ładowanie pliku i odczytanie jego zawartości jest — dzięki klasom wejścia-wyjścia Javy — bar­dzo proste. Tworzymy metodę pomocniczą odczytującą określony plik i składującą pary wartości w strukturze Hashtable. Obiekt ten może zostać następnie przekazany innej metodzie po­mo­c­ni­czej, ładującej i rejestrującej poszczególne procedury obsługi. W tej przykładowej metodzie nie sprawdzamy zbyt intensywnie, czy nie wystąpiły błędy, a każda linijka nie będąca parą wartości jest po prostu ignorowana; w serwerze produkcyjnym prawdopodobnie posłużylibyśmy się bar­dziej zaawansowanym kodem (zresztą dodanie kodu sprawdzającego błędy byłoby naprawdę pro­ste). Po odnalezieniu wiersza zawierającego parę wartości jest on rozbijany na części, a identyfikator i na­zwa klasy zostają zachowane w strukturze Hashtable. Dodamy teraz instrukcje importujące oraz nową metodę getHandlers():

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.util.Hashtable;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.WebServer;

...

/**

* <p>

* Metoda przetwarzająca plik konfiguracyjny

* (w minimalistyczny sposób) i odczytująca dostarczone

* definicje procedur obsługi.

* </p>

*

* @return <code>Hashtable</code> - pary identyfikator/klasa.

* @throws <code>IOException</code> - zgłaszany w razie wystąpienia

* błędu przy przetwarzaniu/odczycie pliku.

*/

private Hashtable getHandlers() throws IOException {

Hashtable handlers = new Hashtable();

BufferedReader reader =

new BufferedReader(new FileReader(configFile));

String line = null;

while ((line = reader.readLine()) != null) {

// Składnia to "nazwaProceduryObslugi, klasaProceduryObslugi"

int comma;

// Pomijamy komentarze

if (line.startsWith("#")) {

continue;

}

// Pomijamy wiersze puste lub bezużyteczne

if ((comma = line.indexOf(",")) < 2) {

continue;

}

// Dodajemy nazwę i klasę procedury obsługi

handlers.put(line.substring(0, comma),

line.substring(comma+1));

}

return handlers;

}

...

Zamiast dodawać kod zachowujący wynik działania takiej metody, wyniku tego użyjemy w fun­k­cji danych wejściowych metody przetwarzającej Hashtable i dodającej poszczególne proce­du­ry obsługi do serwera. Kod wykonujący to zadanie nie jest skomplikowany; warto jedynie zazna­czyć, że metoda addHandler() klasy WebServer wymaga podania jako parametru egzemplarza kla­sy. Trzeba więc pobrać nazwę klasy do zarejestrowania ze struktury Hashtable, załadować tę klasę do wirtualnej maszyny Javy za pomocą Class.forName() i stworzyć jej egzemplarz za pomocą newInstance(). Właśnie w ten sposób postępuje się w programach ładujących klasy i innych aplikacjach dynamicznych w Javie; Czytelnik może nie znać tej metody, jeśli do­piero zaczyna przygodę z tym językiem lub nie musiał tworzyć dynamicznie egzemplarzy klas z nazwy tekstowej. Po załadowaniu klasy w ten sposób, zarówno ona, jak i jej identyfikator prze­ka­zywane są do metody addHandler() i pętla jest kontynuowana. Po załadowaniu całej zawar­tości Hashtable serwer jest gotowy do działania. Do „przechodzenia” po kluczach Hashtable używamy klasy Enumeration, a więc trzeba dodać odpowiednią instrukcję importującą:

package com.oreilly.xml;

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.util.Enumeration;

import java.util.Hashtable;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.WebServer;

...

/**

* <p>

* Tutaj zarejestrujemy procedury dostarczone do serwera

* (zazwyczaj z <code>{@link #getHandlers()}</code>.

* </p>

*

* @param handlers <code>Hashtable</code> procedur do zarejestrowania

*/

private void registerHandlers(Hashtable handlers) {

Enumeration handlerNames = handlers.keys();

// Przetwarzanie procedur obsługi

while (handlerNames.hasMoreElements()) {

String handlerName = (String)handlerNames.nextElement();

String handlerClass = (String)handlers.get(handlerName);

// Tę procedurę obsługi dodajemy do serwera

try {

server.addHandler(handlerName,

Class.forName(handlerClass).newInstance());

System.out.println("Zarejestrowana w serwerze nazwa " + handlerName +

" odpowiada klasie " + handlerClass);

} catch (Exception e) {

System.out.println("Nie jest możliwe zarejestrowanie procedury " +

handlerName + " odpowiadającej klasie " +

handlerClass);

}

}

}

...

Mamy tu do czynienia z uzupełnieniem wcześniejszej metody getHandlers(); faktycznie jako wejście pobierany jest właśnie jej wynik działania. Wartości String z klasy Hashtable są pobierane i rejestrowane. Serwer załaduje i udostępni do zdalnych wywołań wszystkie procedury obsługi podane w pliku konfiguracyjnym. Należy pamiętać, że metody te można byłoby równie do­brze skonsolidować do postaci jednej, większej metody. Jednakże obie opisywane metody mają róż­ne cele: getHandlers() przetwarza plik, a registerHandlers() rejestruje procedury obsługi, gdy tylko informacje o nich zostaną udostępnione. To wszystko umożliwia prostą zmianę sposobu odczytywania pliku konfiguracyjnego (można nawet sprawić, że informacje będą odczy­ty­wane z bazy danych), bez konieczności pamiętania o samym sposobie rejestrowania procedur obsługi. W następnym rozdziale pójdziemy dalej — usuniemy metodę getHandlers(), zastę­pu­jąc ją klasą pomocniczą odczytującą te informacje z pliku XML! W tym przypadku podjęcie właściwej decyzji odpowiednio wcześnie (a więc w tej chwili) oszczędzi nam potem wiele pracy związanej ze zmienianiem kodu (w następnym rozdziale).

Po dodaniu tych dwóch metod pomocniczych dodajemy ich wywołanie do metody start() kla­sy serwera.

/**

* <p>

* Uruchomienie serwera.

* </p>

*

* @throws <code>IOException</code> zgłaszany w razie problemów.

*/

public void start() throws IOException {

try {

// Korzystamy z parsera SAX Apache Xerces

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

System.out.println("Uruchamianie serwera XML-RPC...");

server = new WebServer(port);

// Rejestracja procedur obsługi.

registerHandlers(getHandlers());

} catch (ClassNotFoundException e) {

throw new IOException("Błąd ładowania parsera SAX: " +

e.getMessage());

}

}

Wokół dodanych metod budujemy blok try-catch, dzięki czemu będziemy potrafili odróżnić wyjątki występujące w samym serwerze (blok zewnętrzny) od tych związanych z budowaniem procedur obsługi. W tym ostatnim przypadku zgłaszamy błąd jako komunikat generowany przez metody procedur obsługi. Kompilujemy kod, upewniamy się, że plik konfiguracyjny jest na miej­scu, i otrzymujemy serwer gotowy do użycia.

Użyteczny klient

W kliencie nie zastosujemy żadnych nowych koncepcji czy technik; klasa SchedulerClient bę­dzie równie prosta jak HelloClient. Będzie uruchamiała klienta XML-RPC, wywoływała metody procedur obsługi i wyświetlała wynik ich działania. Cały kod znajduje się poniżej. O tym, co się dzieje w programie, informują komentarze; ponieważ tę stronę aplikacji już omówiliśmy, wystarczy teraz tylko skompilować kod podany w przykładzie 10.9.

Przykład 10.9. Klasa SchedulerClient

import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Calendar;

import java.util.Date;

import java.util.Enumeration;

import java.util.Hashtable;

import java.util.Vector;

import helma.xmlrpc.XmlRpc;

import helma.xmlrpc.XmlRpcClient;

import helma.xmlrpc.XmlRpcException;

/**

* <b><code>SchedulerClient</code></b> to klient XML-RPC wysyłający

* żądania XML-RPC do programu <code>Scheduler</code>.

*

* @version 1.0

*/

public class SchedulerClient {

/**

* <p>

* Dodajemy zdarzenia do programu Scheduler.

* </p>

*

* @param <code>XmlRpcClient</code> - klient, z którym się łączymy

*/

public static void addEvents(XmlRpcClient client)

throws XmlRpcException, IOException {

System.out.println("\nDodawanie zdarzeń...\n");

// Parametry zdarzeń.

Vector params = new Vector();

// Zdarzenie na następny miesiąc.

params.addElement("Zweryfikować ostateczną wersję roboczą".);

Calendar cal = Calendar.getInstance();

cal.add(Calendar.MONTH, 1);

params.addElement(cal.getTime());

// Dodanie zdarzenia.

if (((Boolean)client.execute("scheduler.addEvent", params))

.booleanValue()) {

System.out.println("Zdarzenie zostało dodane.");

} else {

System.out.println("Zdarzenie nie mogło zostać dodane.");

}

// Zdarzenie na jutro.

params.clear();

params.addElement("Wysłać ostateczną wersję roboczą");

cal = Calendar.getInstance();

cal.add(Calendar.DAY_OF_MONTH, 1);

params.addElement(cal.getTime());

// Dodanie zdarzenia.

if (((Boolean)client.execute("scheduler.addEvent", params))

.booleanValue()) {

System.out.println("Zdarzenie zostało dodane.");

} else {

System.out.println("Zdarzenie nie mogło zostać dodane.");

}

}

/**

* <p>

* Wyświetlenie zdarzeń obecnych w programie Scheduler.

* </p>

*

* @param <code>XmlRpcClient</code> - klient, z którym się łączymy.

*/

public static void listEvents(XmlRpcClient client)

throws XmlRpcException, IOException {

System.out.println("\nWyświetlanie zdarzeń...\n");

// Pobieranie zdarzeń z planu zadań.

Vector params = new Vector();

Vector events =

(Vector)client.execute("scheduler.getListOfEvents", params);

for (int i=0; i<events.size(); i++) {

System.out.println((String)events.elementAt(i));

}

}

/**

* <p>

* Statyczny punkt wyjścia programu.

* </p>

*/

public static void main(String args[]) {

try {

// Korzystamy z implementacji parsera SAX Apache Xerces.

XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Łączenie z serwerem.

XmlRpcClient client =

new XmlRpcClient("http://localhost:8585/");

// Dodanie zdarzeń.

addEvents(client);

// Wyświetlenie zdarzeń

listEvents(client);

} catch (Exception e) {

System.out.println(e.getMessage());

}

}

}

Warto zauważyć, że dodawanie zdarzeń następuje w kolejności odwrotnej do kolejności ich wystę­po­wania. Nasz serwer sam zajmie się ich posortowaniem (metoda sortEvents()) i gdy wy­wo­ła­my metodę getListOfEvents(), zdarzenia zostaną zwrócone we właściwej kolejności.

Mów do mnie jeszcze...

Po wpisaniu kodu procedury obsługi serwera i klienta wszystkie pliki źródłowe należy skom­pi­lować. Trzeba również stworzyć plik konfiguracyjny, w którym umieszczone będą procedury ob­sługi do zarejestrowania w serwerze XML-RPC. Następnie najpierw uruchamiamy serwer XML-RPC jako oddzielny proces:

D:\prod>start java com.oreilly.xml.LightweightXmlRpcServer 8585 D:\prod\conf\xmlrpc.conf

...czego odpowiednikiem w systemach Unix jest:

[adam@maly ch10]$ java com.oreilly.xml.LightweightXmlRpcServer 8585 conf/xmlrpc.conf &

Serwer powinien wyświetlić procedury obsługi odczytane z pliku konfiguracyjnego oraz odpo­wia­dające im nazwy:

Uruchamianie serwera XML-RPC...

Zarejestrowana w serwerze nazwa scheduler odpowiada klasie Scheduler.

Zarejestrowana w serwerze nazwa hello odpowiada klasie HelloHandler.

0x01 graphic

Jeśli wcześniej nie zatrzymaliśmy utworzonego wcześniej serwera XML-RPC, Hel­loServer, otrzymamy komunikat o błędzie wynikający z próby uruchomienia kolej­nego serwera na tym samym porcie. Przed uruchomieniem obecnego serwera należy zatrzymać poprzedni.

Teraz uruchamiamy klienta i obserwujemy wyniki:

[adam@maly ch10]$ java SchedulerClient

Dodawanie zdarzeń...

Zdarzenie zostało dodane.

Zdarzenie zostało dodane.

Wyświetlanie zdarzeń...

Zdarzenie "Wysłać ostateczną wersję roboczą" zaplanowane na 03:42 PM 01/20/2001

Zdarzenie "Zweryfikować ostateczną wersję roboczą" zaplanowane na 03:42 PM 02/19/2001

W czasie dodawania i wyświetlania zdarzeń nie powinniśmy odczuć znacznego opóźnienia, a mi­mo to zdarzenia są sortowane przez serwer. Odbywa się to po prostu w oddzielnym wątku wir­tu­al­nej ma­szyny Javy (a trzeba wiedzieć, że sortowanie pęcherzykowe nie należy do szybkich algorytmów!). I tak właśnie napisaliśmy pierwszą użyteczną aplikację XML-RPC.

Praktyczne zastosowania technologii XML-RPC

W zakończeniu rozdziału zostaną omówione zagadnienia zwiazane z wykorzystywaniem XML-RPC w rzeczywistych zastosowaniach. Czytelnik po raz kolejny będzie mógł się przekonać, że XML używany jest nie dlatego, że to taka nowoczesna i modna technologia, ale dlatego, że w niektórych sytuacjach po prostu nie ma sobie równych. Cała wiedza zawarta w tej książce, specyfikacje ota­czające XML i inne książki opisujące ten standard — wszystko to na nic się nie zda, jeśli pro­gramista nie będzie wiedział, jak korzystać z języka XML oraz modelu XML-RPC poprawnie! W niniejszej części zostaną przedstawione typowe problemy trapiące programistów XML-RPC.

Gdzie właściwie jest ten XML?

Po lekturze dotychczasowej części rozdziału Czytelnik może zastanawiać się, dlaczego w two­rzo­nym kodzie nie zostały użyte interfejsy SAX, DOM lub JDOM. Co więcej, kod prawie wcale nie bazował bezpośrednio na XML-u. Wynika to stąd, że kodowanie i dekodowanie żądań przesy­ła­nych pomiedzy klientem a serwerem wykonywane było przez biblioteki XML-RPC. Choć XML nie został użyty bezpośrednio, to jednak całe to rozwiązanie oparte jest właśnie na technologii XML. Proste żądanie wysyłane do metody sayHello() zostało tak naprawdę przetłumaczone na wywołanie HTTP o postaci przedstawionej w przykładzie 10.10.

Przykład 10.10. Żądanie XML-RPC po rozkodowaniu

POST /RPC2 HTTP/1.1

User-Agent: Tomcat Web Server/3.1 Beta (Sun Solaris 2.6)

Host: newInstance.com

Content-Type: text/xml

Content-length: 234

<?xml version="1.0"?>

<methodCall>

<methodName>hello.sayHello</methodName>

<params>

<param>

<value><string>Brett</string></value>

</param>

</params>

</methodCall>

Biblioteki XML-RPC po stronie serwera otrzymują takie żądanie i rozkodowują je, dopasowując odpowiednią metodę procedury obsługi (o ile taka istnieje). Następnie żądana metoda Javy jest uruchamiana, a serwer ponownie koduje wynik do postaci XML, tak jak to pokazano w przy­kładzie 10.11.

Przykład 10.11. Odpowiedź XML-RPC po zakodowaniu

HTTP/1.1 200 OK

Connection: close

Content-Type: text/xml

Content-length: 149

Date: Mon, 11 Apr 2000 03:32:19 CST

Server: Tomcat Web Server/3.1 Beta-Sun Solaris 2.6

<?xml version="1.0"?>

<methodResponse>

<params>

<param>

<value><string>Hello Brett</string></value>

</param>

</params>

</methodResponse>

Komunikacja trwa, chociaż użytkownik wcale nie musiał zaprzątać sobie głowy szczegółami.

Egzemplarze współużytkowane

Przedstawione przykłady zobrazowały sposoby wykorzystania statycznych obiektów do współ­użytkowania danych przez wiele egzemplarzy tej samej klasy. Jednakże istnieją także sytuacje, w któ­­rych współużytkowany jest sam egzemplarz. Nie musi to wynikać z zastosowania modelu XML-RPC, ale z konieczności różnego użycia klasy po stronie serwera. Na przykład Java do­pusz­cza two­rzenie tylko jednego egzemplarza danej klasy i egzemplarz ten współużytkowany jest przez wszystkie aplikacje. Zazwyczaj uzyskuje się to za pomocą statycznej metody getInstance(), a nie po­przez konstruowanie obiektu:

Scheduler scheduler;

// Pobieramy jeden egzemplarz, obsługiwany przez klasę Scheduler

scheduler = Scheduler.getInstance()

// Dodajemy zdarzenie ważne w tej chwili

scheduler.addEvent("Piknik", new Date());

Aby zagwarantować, że żadne klasy nie stworzą bezpośrednio egzemplarza klasy Scheduler, kon­struktor zazwyczaj otrzymuje status prywatnego lub chronionego. Powoduje to, że do stwo­rzenia egzemplarza klienty muszą użyć przedstawionego wyżej kodu; jednak rozwiązanie takie pociąga za sobą trudności z użyciem takiej klasy jako procedury obsługi XML-RPC. Czytelnik pamięta, że zarejestrowanie procedury obsługi odbywało się poprzez stworzenie egzemplarza jej klasy. Jednak klasa WebServer wymaga jako parametru poprawnego egzemplarza, ale nie­ko­niecznie nowego egzemplarza. Na przykład zupełnie poprawnym sposobem dodania procedury obsługi jest zastosowanie poniższego kodu:

WebServer server = new WebServer(8585);

// Tworzymy nową klasę procedury obsługi.

HelloHandler hello = new HelloHandler();

server.addHandler("hello", hello);

Klasa serwera nie rozróżnia powyższych sposobów, o ile tylko egzemplarz klasy procedury ob­sługi stworzono przed przekazaniem jej metodzie addHandler(). Tak więc, jeśli chcemy dodać egzemplarz do opisanej wyżej jednorodnej klasy Scheduler, kod musimy nieco zmienić:

WebServer server = new WebServer(8585);

// Przekazujemy jednorodny egzemplarz

server.addHandler("scheduler", Scheduler.getInstance());

Przekazywany jest egzemplarz współużytkowany — wszystko odbywa się tak, jakby egzemplarz kla­sy został utworzony poprzez konstruktor za pomocą słowa kluczowego new; wszystkie in­for­ma­cje współużytkowane w klasie jednorodnej zostają zachowane. Czytelnik będzie mógł się prze­konać, że wiele klas wykorzystywanych w usługach typu XML-RPC tworzonych jest jako klasy jednorodne, co zapobiega użyciu statycznych danych. Zastosowanie egzemplarza współ­użyt­ko­wa­ne­go umożliwia przechowywanie danych w zmiennych przynależnych, a więc ten jeden egzem­plarz operuje na tych zmiennych w celu obsłużenia wszystkich żądań klienta.

Z serwletem czy bez?

Ostatnio jako serwer XML-RPC często wykorzystywany jest program działający jako serwlet (wię­cej informacji o serwletach można znaleźć w książce Jasona Huntera Java Servlet Programming, wyd. O'Reilly & Associates). Nawet w dystrybucji XML-RPC dla Javy znajduje się serwlet. Uży­cie serwleta w ten sposób jest zupełnie poprawne i często spotykane — serwlet zajmuje się wtedy wyłącznie obsługą żądań XML-RPC, jednakże nie zawsze jest to rozwiązanie najlepsze.

Jeśli programista ma komputer, który musi obsłużyć inne żądania HTTP związane z wyko­ny­wa­niem zadań w Javie, wtedy na pewno użycie mechanizmu serwletów do obsługi takich żądań mija się z celem. W takim przypadku użycie serwleta jako serwera XML-RPC jest dobrym roz­wią­za­niem. Jednak jedną z zalet XML-RPC jest możliwość odseparowania złożonych, „zasobożernych” zadań klas procedur obsługi od pozostałego kodu aplikacji. Nasza klasa Scheduler mogłaby zo­stać umiesz­czona na serwerze wraz z klasami wykonującymi złożone indeksowanie, modelowanie algorytmiczne czy transformacje graficzne. Wszystkie te funkcje bardzo obniżają wydajność klien­tów. Jednak dodanie mechanizmu serwletów i przyjmowanie żądań aplikacji dotyczących innych zadań oraz obsługi XML-RPC zmniejsza moc przetwarzania dostępną dla klas procedur obsługi. W takim przypadku jedyne żądania przychodzące do serwera powinny dotyczyć właśnie klas pro­cedur obsługi.

W przypadku, gdy przyjmowane są tylko żądania XML-RPC (jak wykazano powyżej), zastoso­wa­nie serwleta jako serwera XML-RPC rzadko zdaje egzamin. Dostępna klasa WebServer jest bardzo mała, „lekka” i opracowana właśnie do obsługi XML-RPC poprzez HTTP. Mechanizm ser­w­letów obsługuje dowolne żądania HTTP i nie jest przystosowany do żądań XML-RPC. Z biegiem czasu zacznie być zauważane obniżenie wydajności związane z użyciem mechanizmu serwletów zamiast klasy WebServer. Jeśli więc serwer nie będzie obsługiwał innych zadań niż związane z XML-RPC, lepiej pozostać przy „lekkim” serwerze XML-RPC, opracowanym właśnie do takich zastosowań.

Co dalej?

Teraz Czytelnik powinien już potrafić stosować XML na różne sposoby, tak jak to zostało przed­sta­wione w rozdziałach „tematycznych”. W następnym rozdziale kontynuowane będzie oma­wia­nie praktycznych zastosowań XML-a. Tym razem skoncentrujemy się na wykorzystaniu tego języka w konfiguracji. Języka XML używa się w plikach wdrożeniowych Enterprise JavaBeans, wielu serwerów i coraz większej liczby aplikacji. W kolejnym rozdziale zostaną omówione przy­czyny tej ekspansji XML-a; zostaną również przedstawione sposoby zastosowania tego języka do wyłącznych celów aplikacji, a nie tylko do przesyłania danych pomiędzy aplikacjami. Czytelnik do­wie się, dlaczego w najbliższej przyszłości nie grozi nam likwidacja baz danych i serwerów usług katalogowych, dowie się również, gdzie warto stosować XML, a gdzie zastosowanie tego języka nie jest uzasadnione.

Oczywiście, typy struct i array muszą zawierać tylko inne dozwolone typy XML-RPC.

Obecnie nie istnieją biblioteki XML-RPC obsługujące SAX 2.0 i implementujące interfejs XMLReader. Oczekuje się, że wkrótce ten stan rzeczy ulegnie zmianie; ponieważ klasa Apache Xerces SAXParser implementuje zarówno stary, jak i nowy interfejs, po aktualizacji XML-RPC do SAX-a 2.0 nie trzeba będzie wprowadzać w kodzie żadnych zmian. Jeśli jednak korzystamy z parsera innego producenta, może się okazać konieczne jawne określenie klasy SAX 2.0.

Jeśli Czytelnik korzysta z systemu Unix i chce uruchomić usługę na porcie niższym niż 1024, musi być zalogowany jako root. Aby uniknąć problemu, warto skorzystać z wyższego portu, tak jak to pokazano na przykładzie.

270 Rozdział 10. XML-RPC

Co dalej? 271

C:\Helion\Java i XML\jAVA I xml\10-08.doc — strona 270

C:\Helion\Java i XML\jAVA I xml\10-08.doc — strona 271

C:\Helion\Java i XML\jAVA I xml\10-08.doc — strona 239



Wyszukiwarka

Podobne podstrony:
developerWorks Tutorial XML programming in Java (1999)
Java i XML, programowanie, Java
Java i XML, programowanie, Java
05-08, Programowanie, ! Java, Java i XML
02-08, Programowanie, ! Java, Java i XML
12-08, Programowanie, ! Java, Java i XML
00-08-orig, Programowanie, ! Java, Java i XML
01-08, Programowanie, ! Java, Java i XML
14-08, Programowanie, ! Java, Java i XML
zasady grupy, java, javascript, oprogramowanie biurowe, programowanie, programowanie 2, UTK, systemy
r12-05, Programowanie, ! Java, Java Server Programming
Programowanie współbieżne i rozproszone w języku Java stpiczynski
Java Sztuka programowania jaszpr
JAVA 03 konstrukcja programu
r20-05, Programowanie, ! Java, Java Server Programming
wyklad5.cpp, JAVA jest językiem programowania obiektowego
Java Zadania z programowania z przykładowymi rozwiązaniami
Java Programowanie Sieciowe Podstawy Javy id 226331

więcej podobnych podstron