Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen!Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau
Przedmowa XML, XML, XML... Ten skrót pojawia się na czapeczkach i koszulkach. W każdym czasopiśmie technicznym, w radiu, w czasopismach kobiecych... no, może sprawy nie zaszły jeszcze tak daleko, ale niewiele brakuje. XML, rozszerzalny język znaczników (ang. Extensible Markup Language), wydaje się pojawiać w każdej branży technicznej, a szczególnie często słychać o nim w środowisku programistów języka Java®. Aplikacji nie uważa się już za produkt przemysłowy, jeśli nie użyto w niej w jakiś sposób języka XML. Wreszcie powstała możliwość nowych zastosowań dla starych, firmowych systemów, a przedsiębiorstwa oszczędzają miliony i miliardy dolarów na integracji systemów — wszystko dzięki tym trzem literkom. Programiści Javy będą zmuszeni do przyswajania sobie jeszcze jednej technologii, a zadanie wydaje się być tym trudniejsze, im bardziej się w nie zagłębić. Drogę do XML-a wytyczają kolejne skróty: XSL, XPath, RDF, XML Schema, DTD, PI, XSLT, XSP, JAX®, SAX, DOM i inne. I nie ma już chyba dyrektora, który nie wymagałby od swojego personelu informatycznego znajomości XML-a! Kiedy na początku 1998 roku XML zyskał status specyfikacji formalnie określonej przez World Wide Web Consortium, niewiele osób zgodziłoby się uznać, że powstała właśnie najwspanialsza technologia od czasów Javy (a może jeszcze wspanialsza!). Ale już dwa lata później XML i grupa technologii służących do manipulacji tym językiem zaczęły stanowić podstawowy sposób reprezentacji danych w Javie. XML ma szansę wnieść do formatu danych to, co Java wniosła do języków programowania — całkowitą przenośność. I właściwie to zamierzenie twórców Javy osiągnąć można tylko dzięki językowi XML; przenośność Javy bardzo ucierpiała ze względu na używane od lat, firmowe formaty danych — aplikacja mogła zostać uruchomiona na wielu platformach, ale nie w wielu firmach, na standardowym formacie danych. XML może rozwiązać ten problem — powoduje, że te firmowe formaty danych zaczynają być zbędne i umożliwia wzajemne komunikowanie się systemów za pomocą standardowej reprezentacji danych. To książka o języku XML, ale napisana głównie z myślą o programistach Javy. Zarówno XML, jak i Java są potężnymi narzędziami. W tej książce jednak uwaga skupiona zostanie na tym, jak ze sobą współpracują — to właśnie dzięki tej współpracy XML staje się naprawdę potężną technologią. Przedstawione zostaną różne słowniki XML. Czytelnik nauczy się tworzyć, zawężać i przekształcać XML, pozna także wszystkie interfejsy programistyczne (API) do obsługi języka XML z poziomu kodu Javy. W książce zostaną także podjęte najciekawsze zagadnienia dotyczące języka XML: dynamiczna zawartość, usługi powiadamiania, e-biznes i składnice danych. Za każdym razem będzie stosowany ten sam, wąski punkt widzenia programisty, któremu zależy na wykonaniu konkretnego zadania z użyciem opisywanych narzędzi. Zostaną przedstawione narzędzia, jakie
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 7
8
Przedmowa
udostępnia XML; a jeśli istnieje coś, co — choć popularne! — nie jest zbyt użyteczne, zostanie tylko pobieżnie omówione. Jeżeli natomiast któraś wyjątkowo przydatna funkcja XML-a sprawia wrażenie „ukrytego rodzynka” — zostanie on wydobyty i opisany pod kątem użyteczności. Java i XML ma służyć pomocą Czytelnikowi. Nie jest pozycją referencyjną ani nie ma na celu reklamowania standardu XML. Połowa tej książki to kod — praktyczny i działający. Można go także pobrać z Internetu, ale umieszczono go w książce po to, aby Czytelnik mógł prześledzić proces tworzenia różnych aplikacji XML. Czytelnik powinien więc raczej zagłębiać się w te przykłady, a nie je pomijać. Jest tutaj przedstawiony także nowy interfejs API do manipulacji XML-em z poziomu Javy — został on opisany w pełni, z przykładami. To książka dla programisty Javy, dlatego zastosowano podejście praktyczne. To nie jest ani teoretyczny, ani pobieżny przegląd „fajnej” technologii. Tam, gdzie to możliwe, unikamy słów-haseł; tam, gdzie nie — podajemy ich precyzyjne definicje. Cały kod oraz rozwiązania z tej książki zostały ręcznie wpisane do edytora i przetestowane. Są tutaj po to, by pomóc Czytelnikowi w solidnym opanowaniu Javy i XML-a.
Organizacja książki Książka została podzielona w szczególny sposób. W pierwszej połowie (rozdziały 1. do 7.) został przedstawiony standard XML oraz najważniejsze interfejsy API Javy do jego obsługi. Może nie są to rozdziały niezwykle interesujące, jednak powinny być czytane w proponowanej kolejności, a przynajmniej dokładnie przejrzane — nawet jeśli XML nie jest Czytelnikowi obcy. Rozdział 8. to jakby fragment przejściowy pomiędzy dwiema częściami książki. Opisano w nim niezwykle ciekawy nowy interfejs API do obsługi XML-a z poziomu Javy — JDOM. Ten rozdział trzeba przeczytać koniecznie, ponieważ publikacja tego interfejsu zbiegnie się z wprowadzeniem niniejszej książki do sprzedaży, a tutaj przedstawiono pełne materiały referencyjne odnośnie tego API (interfejs ten, który autor utworzył wraz z Jasonem Hunterem, ukierunkowany jest właśnie na rozwiązywanie problemów z wykorzystaniem Javy i XML-a!). W pozostałej części książki (rozdziały 9. do 14.) omawiane są specyficzne tematy związane z XML-em, nieustannie poruszane na konferencjach i w samouczkach programistów. Analiza tych tematów pozwoli Czytelnikowi maksymalnie wykorzystać XML we własnych aplikacjach — i to od zaraz! Książkę zamykają dwa dodatki. Poniżej przedstawiony jest skrótowy opis poszczególnych rozdziałów: Rozdział 1. Wprowadzenie Omówienie alfabetu XML-a i próba znalezienia odpowiedzi na pytanie, dlaczego standard ten jest tak istotny dla obecnych i przyszłych aplikacji stosowanych w przedsiębiorstwach. Rozdział 2. Tworzenie dokumentów XML Omówienie procesu budowania dokumentu XML od podstaw; zastosowanie najważniejszych konstrukcji XML, takich jak elementy, atrybuty, encje. Rozdział 3. Przetwarzanie kodu XML Przedstawienie interfejsu API Javy służącego do obsługi XML-a — Simple API for XML (SAX). Szczegółowo opisano proces przetwarzania oraz zdarzenia udostępniane przez interfejs SAX, przeznaczone do wykorzystania przez programistów. Rozdział 4. Zawężanie danych XML Przedstawienie dwu sposobów zawężenia dokumentu XML: zastosownie definicji typu dokumentu (DTD) oraz XML Schema.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 8
Organizacja książki
9
Rozdział 5. Sprawdzanie poprawności składni XML-a Uzupełnienie poprzedniego rozdziału. Omówiono, jak za pomocą poznanego interfejsu SAX umożliwić weryfikację składni dokumentu oraz reagować, gdy dokument wykracza poza narzucone zawężenia. Rozdział 6. Przekształcanie kodu XML Opis rozszerzalnego języka arkuszy stylów XSL oraz innych istotnych komponentów służących do przekształcania dokumentów XML z jednego formatu na inny. Omówiono różne metody konwersji dokumentów XML na inne formaty tekstowe oraz sposób wykorzystania obiektów formatujących do konwersji na formaty binarne. Rozdział 7. Czerpanie z XML-a Kontynuacja tematu przekształcania dokumentów XML. Przedstawiono procesory transformacji XML oraz sposoby konwersji dokumentów XML na inne formaty. Opisano również model obiektowy dokumentu (DOM) i wyjaśniono, w jaki sposób można za jego pomocą obsłużyć dane XML. Rozdział 8. JDOM Omówienie interfejsu Java API for XML Parsing (JAXP) i próba przeanalizowania, jak duże znaczenie w XML-u ma niezależność od rozwiązań firmowych. Zaprezentowano też interfejs JDOM, wskazano przyczyny jego powstania, szczegółowo opisano sposób korzystania z niego oraz porównano go z interfejsami SAX oraz DOM. Rozdział 9. Struktury publikacji WWW Analiza struktur publikacji WWW i wybór najodpowiedniejszej. Omówiono także strukturę Apache Cocoon oraz wyjaśniono, w jaki sposób korzystać z niej przy budowaniu dynamicznej zawartości stron WWW. Rozdział 10. XML-RPC Przedstawienie zdalnych wywołań procedur (RPC), uwzględnienie ich znaczenia w systemach rozproszonych w porównaniu z RMI oraz wyjaśnienie, dlaczego technologie RPC w przypadku niektórych problemów tak wiele zyskują dzięki XML-owi. Omówienie biblioteki XML-RPC Javy oraz konstrukcji klientów XML-RPC. Rozdział 11. XML na potrzeby konfiguracji Omówienie sposobów tworzenia danych konfiguracyjnych w postaci XML i wyjaśnienie, dlaczego format ten jest tak istotny w aplikacjach wieloplatformowych, szczególnie w systemach rozproszonych. Rozdział 12. Tworzenie danych XML w języku Java Przedstawienie procesu generowania i modyfikowania formatu XML z poziomu Javy przy wykorzystaniu mechanizmów działających po stronie serwera, takich jak serwlety. Wskazano też, na co trzeba zwrócić uwagę przy dokonywaniu mutacji XML. Rozdział 13. Operacje typu firma-firma Omówienie sposobu komunikacji pomiędzy firmami i wewnątrz firmy z wykorzystaniem przenośnego formatu danych XML. Posłużą do tego przykładowe aplikacje, napisane w różnych językach i porozumiewające się ze sobą za pomocą XML-a. Rozdział 14. XML Schema Omówienie przyczyn niezwykłego zainteresowania specyfikacją XML Schema oraz wyjaśnienie, dlaczego technologie Java i XML Schema nawzajem się uzupełniają.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 9
10
Przedmowa
Dodatek A. Interfejs API — materiał referencyjny W tym dodatku wymieniono wszystkie klasy, interfejsy i metody dostępne w interfejsach programistycznych SAX, DOM, JAXP i JDOM. Dodatek B. Funkcje i właściwości SAX 2.0 W tym dodatku wymieniono wszystkie funkcje i właściwości implementacji interfejsu SAX 2.0.
Dla kogo jest ta książka? Podstawą napisania książki było przekonanie, że XML jest już nieodłącznym elementem programowania w Javie. Książka została napisana w taki sposób, że najpierw Czytelnik dowiaduje się, jak w ogóle korzystać z XML-a i Javy. Nie ma tutaj rozważań dotyczących tego, czy powinno się korzystać z XML-a. Programista Javy powinien korzystać z XML-a. Dlatego, jeśli Czytelnik programuje w Javie, chce programować w Javie, zarządza programującymi w Javie lub jest odpowiedzialny albo związany z projektem pisanym w Javie — książka na pewno mu się przyda. Jeśli Czytelnik chce się rozwijać, pogłębiać wiedzę programistyczną, pisać bardziej przejrzysty kod, tworzyć udane projekty w terminie i bez przekraczania budżetu, musi korzystać z danych w formatach firmowych, tworzyć rozproszone systemy lub po prostu ciekaw jest, skąd całe to zainteresowanie XML-em — książka na pewno mu się przyda. Pisząc książkę, starałem się czynić jak najmniej założeń. Nie chcę rozpoczynać omawiania XML-a od takiego poziomu, że zniechęci to część Czytelników. Ale uważam także, że jeśli Czytelnik wydał już pieniądze na tę książkę, to zależy mu na czymś więcej niż tylko na podstawach. Dlatego zakładam jedynie znajomość Javy i zagadnień programowania po stronie serwera (takich jak serwlety Javy czy komponenty Enterprise JavaBean®. Jeśli Czytelnik nie miał jeszcze nigdy do czynienia z Javą albo dopiero rozpoczyna przygodę z tym językiem, być może warto najpierw przeczytać Learning Java Pata Niemeyera i Jonathana Knudsena (wyd. O'Reilly & Associates). Zakładam, że Czytelnik nie wie nic o XML-u, więc zaczynam od samych podstaw. Ale zakładam również, że Czytelnik będzie ciężko pracował i szybko przyswajał wiedzę. Dlatego podstawy zostaną omówione błyskawicznie, by jak najszybciej przejść do bardziej zaawansowanych zagadnień. Materiał nie będzie wielokrotnie powtarzany poza tymi miejscami, gdzie jest to absolutnie koniecznie. Może więc zajść potrzeba ponownego przeczytania wcześniejszych fragmentów. Jeśli Czytelnik chce uczyć się XML-a, trochę zna Javę i przygotowany jest na „wstukanie” przykładowego kodu do swojego ulubionego edytora tekstów, nie powinien mieć większych problemów z przyswojeniem materiału opisanego w książce.
Oprogramowanie i wersje Informacje w tej książce dotyczą XML-a w wersji 1.0 oraz różnych słowników XML w wersjach najbardziej aktualnych w czasie ukończenia książki, tj. w kwietniu 2000 r. Ponieważ rozmaite specyfikacje XML nie uzyskały jeszcze ostatecznej postaci, pomiędzy drukowaną wersją książki a opisywanymi specyfikacjami mogą wystąpić drobne różnice. Cały kod Javy prezentowany w tej książce oparty jest na platformie Java 1.1, za wyjątkiem opisów dotyczących interfejsu JDOM 1.0. Ta odmienność jest odnotowana i uzasadniona w odpowiednim miejscu, w rozdziale 8. Wykorzystano parser Apache Xerces, procesor Apache Xalan oraz biblioteki Apache FOP w najnowszych stabilnych wersjach z kwietnia 2000 r. Zastosowano strukturę publikacji WWW Apache Cocoon w wersji 1.7.3, a biblioteki Javy XML-RPC w wersji 1.0 beta 3. Całe oprogramowanie można pobrać za darmo z adresów http://java.sun.com, http://xml.apache.org oraz http://www.xml-rpc.com.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 10
Konwencje typograficzne
11
Kod źródłowy przykładów, w tym klasy narzędziowe com.oreilly.xml, jest w całości zamieszczony w książce. Zarówno kod źródłowy, jak i postać binarną wszystkich przykładów (w tym szeroką dokumentację Javadoc) można pobrać z adresów http://www.oreilly.com/catalog/javaxml oraz http://www.newInstance.com. Wszystkie przykłady mogące funkcjonować jako serwlety lub nadające się do przekonwertowania na serwlety można obejrzeć w działaniu online pod adresem http://www.newInstance.com. Pełną dystrybucję JDOM 1.0, wraz ze specyfikacją, implementacją referencyjną, kodem źródłowym, dokumentacją interfejsu oraz postacią binarną można pobrać z adresu http://www.jdom.org. Ponadto stworzono drzewo CVS zawierające kod JDOM i pozwalające na uzupełnianie i komentowanie źródeł. Informacje o tym, jak korzystać z CVS-a JDOM, można znaleźć także pod adresem http://www.jdom.org.
Konwencje typograficzne W książce zastosowane zostały następujące konwencje typograficzne: Druk pochyły wykorzystywany jest do oznaczania: • uniksowych ścieżek dostępu, nazw plików i nazw programów, • adresów internetowych (nazw domen i identyfikatorów URL), • nowych, definiowanych terminów. Czcionka proporcjonalna oznacza: • tekst wpisywany w wierszu poleceń i opcje — czyli to, co ma być wpisane dokładnie tak, jak wydrukowano, • nazwy i słowa kluczowe w programach Javy, w tym nazwy metod, zmiennych i klas, • nazwy elementów i znaczników XML-a, nazwy atrybutów i innych konstrukcji XML-a wyglądających tak, jak wyglądałyby w dokumencie XML . Czcionka proporcjonalna pogrubiona służy do oznaczania: • nowo wprowadzonych fragmentów kodu, • fragmentów większego kodu, które właśnie są opisywane w tekście.
Opinie i komentarze Opinie i komentarze dotyczące tej książki można przysyłać do wydawcy: O'Reilly & Associates, Inc. 101 Morris Street Sebastopol, CA 95472 (800) 998-9938 (Stany Zjednoczone lub Kanada) (707) 829-0515 (międzynarodowe lub lokalne) (707) 829-0104 (faks) Można także wysłać list elektroniczny. W celu zapisania się na listę adresową lub zamówienia katalogu, wystarczy wysłać e-mail pod adres [email protected].
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 11
12
Przedmowa
Podziękowania Kiedy tak spoglądam na plik arkuszy składających się na manuskrypt tej książki, to widzę, że w tych kilku akapitach nie udałoby się podziękować wszystkim, którzy przyczynili się do jej powstania. A jednak jest to na pewno prostsze niż omówienie całej tematyki Javy i XML-a, więc za chwilę spróbuję... Gdybym kogoś pominął, bardzo proszę — wybaczcie! Historia tej książki rozpoczęła się w Święto Dziękczynienia w roku 1999. Wtedy to mój redaktor, Mike Loukides, zaproponował mi napisanie tej książki, mimo że w tym czasie zajęty byłem pisaniem innej książki dla O'Reilly. Nieco wahałem się przed odłożeniem na jakieś sześć miesięcy tekstu, którego tworzenie bardzo mnie pochłonęło. Mike jednak przekonywał mnie tak wprawnie, jak potem redagował gotowy tekst do czytelniejszej postaci. Teraz ten czas wspominam jako najciekawszy okres w mojej karierze technicznej i wiem, jak wiele zawdzięczam właśnie Mike'owi: służył mi wskazówkami przy pisaniu trudnych, pierwszych rozdziałów; wysłuchał narzekań, gdy trzykrotnie (tak, trzykrotnie!) zmieniałem rozdział o XML Schema z powodu aktualizacji specyfikacji; wreszcie, kiedy trzeba było trochę odpocząć, okazywał swoje wszechstronne zdolności muzyczne. Bez niego ta książka nie osiągnęłaby tak wysokiego poziomu. Książce mogłem poświęcić tak wiele wysiłku i czasu tylko dzięki rodzinie i przyjaciołom: rodzicom, którzy dzień w dzień przez osiemnaście lat zabiegali o poprawność mojego języka; cioci, która pasjonowała się tym, co robię, nawet jeśli nie wiedziała, o co w tym chodzi; Jody Durret, Carlowi Henry'emu i Pam Merryman, którzy poświęcili bardzo dużo czasu na zrobienie ze mnie autora książek; Gary'emu i Shirley Greathouse'om, którzy zawsze przypominali, abym wytrwale parł naprzód i nie osiadał na laurach; oraz moim dziadkom, Deanowi i Gladys McLaughlinom, którzy cały czas mnie wspierali. Nad merytoryczną poprawnością i precyzją książki czuwali liczni recenzenci techniczni: Marc Loy, Don Weiss, George Reese (którego komentarze spowodowały dodanie całego rozdziału!), Matthew Merlo i James Duncan Davidson. Szczególną pomoc okazał James — pieczołowicie punktował drobne błędy i był brutalnie szczery, dzięki czemu nigdy nie zapomniałem, że jestem przede wszystkim programistą, a dopiero potem autorem książek. Bardzo dużo zawdzięczam Jasonowi Hunterowi, autorowi Java Servlet Programming (O'Reilly & Associates). Choć pisanie tej książki rozpoczęło się w listopadzie 1999 roku, to przeżyła ona swoiste odrodzenie w marcu 2000, kiedy to Jason i ja całe popołudnie spędziliśmy na trawniku w Santa Clara, narzekając na istniejące interfejsy API Javy do obsługi XML-a. Te utyskiwania zaprocentowały w dwójnasób — przede wszystkim, stworzyliśmy opisywany w tej książce interfejs JDOM (przy pomocy i zachętach ze strony Jamesa Davidsona z Sun Microsystems). Uważamy, że interfejs ten znacząco przyczyni się do zacieśnienia współpracy pomiędzy XML-em i Javą i spowoduje, że XML z punktu widzenia Javy będzie standardem przejrzystym i użytecznym, a nie zagmatwanym i nieprzydatnym. Po drugie, bardzo zaprzyjaźniliśmy się z Jasonem. Pomógł mi w niełatwym procesie tworzenia książki. Niejednokrotnie siedzieliśmy do późnych godzin nocnych, zastanawiając się, co można zrobić by JDOM i inne programy działały bardziej intuicyjnie. Na pewno ta książka nie powstałaby, gdyby nie moja żona, Leigh. Jakimś cudem wytrzymała i nie wyrzuciła mnie z domu przez te ostatnie 6 miesięcy, w ciągu których najczęściej byłem zmęczony, nieprzystępny i w ogóle zajęty. Te krótkie chwile, jakie udawało nam się zdobyć dla siebie po moim pisaniu i pełnoetatowej pracy konsultanta, sprawiały, że cała moja praca wydawała mi się więcej warta. Tęsknię już za nią bardzo i nie mogę się doczekać, kiedy wrócę do zwyczajnego try-
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 12
Podziękowania
13
bu życia właśnie z nią, z naszymi trzema bassetami (Charlie, Molly i Daisy) i moimi labradorami (Sethem i Mosesem). Dziękuję też mojemu dziadkowi, Robertowi Earlowi Burdenowi, który nie doczekał wydania tej książki. Był takim człowiekiem, jakim ja zawsze chciałem być. Dziękuję mu za to, że nauczył mnie, iż nigdy nie powinienem poprzestawać tylko na tym, czego oczekują ode mnie inni.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 13
14
Przedmowa
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 14
1 Wprowadzenie XML. W ciągu ostatnich dwóch lat chyba każdy programista w którymś momencie poczuł ciarki na plecach związane z tym skrótem. Ciarki na plecach, choć z różnych powodów: konieczność wyuczenia się kolejnego skrótu, ekscytacja nową technologią, zagubienie. I, co ciekawe, każda z tych reakcji na XML jest uzasadniona. Tak, trzeba zapamiętać nowy skrót, a także skróty jemu towarzyszące: XSL, XSLT, PI, DTD, XHTML i inne. Tak, XML oferuje nowe możliwości: to, co Java wniosła w przenośność kodu, XML ma wnieść w przenośność danych. W ostatnich miesiącach Sun promuje nawet to rozwiązanie następującym sloganem: „Java + XML = przenośny kod + przenośne dane”. I wreszcie — tak, XML powoduje pewne zagubienie. Spróbujemy przybliżyć i rozgryźć XML — będziemy omawiać go tylko na tyle ogólnie i abstrakcyjnie, żeby nie popaść w bezużyteczność, i tylko na tyle szczegółowo, żeby nie tworzyć kolejnej suchej specyfikacji. To książka dla programisty Javy, który chce poznać i nauczyć się korzystać z narzędzi operujących na tej technologii. Przed programistą współczesnej aplikacji WWW stoi wiele problemów, których dziesięć lat temu nie było nawet w prognozach. Pojawiła się konieczność uruchamiania szybkich i bezawaryjnych systemów rozproszonych na przestrzeni tysięcy kilometrów, konieczność pobierania danych z systemów heterogenicznych, baz danych, usług katalogowych i aplikacji bez utraty choćby jednej cyfry po przecinku. Aplikacje muszą porozumiewać się nie tylko z innymi komponentami danej firmy, ale także z innymi systemami biznesowymi — często znajdującymi się w innych firmach i zbudowanymi w oparciu o inne technologie. Klienty to już nie tylko klienty uproszczone (ang. thin client), ale całe przeglądarki WWW obsługujące HTML, telefony komórkowe z obsługą protokołu aplikacji bezprzewodowych (WAP) czy asystenty osobiste („organizery”) obsługujące zupełnie inne języki znaczników. Oreganizacja danych i ich przetwarzanie — oto podstawowe zadania współczesnych aplikacji. XML pozwala programiście sprostać wszystkim tym wyzwaniom. Programiści Javy mają zaś jeszcze do dyspozycji arsenał interfejsów umożliwiających korzystanie z XML-a i jego towarzyszy bez konieczności opuszczania zintegrowanego środowiska programistycznego Javy (Integrated Development Environment — IDE). Jeśli to wszystko wydaje się zbyt piękne, by było prawdziwe — warto czytać dalej. Poznamy zalety i wady interfejsów API Javy służących do przeprowadzania operacji na XML-u, a także korzyści płynące z zastosowania najnowszych specyfikacji XML-a. Cały czas będziemy przyjmować punkt widzenia programisty. Ta książka nie mówi o tym, dlaczego powinno się korzystać z XML-a, ale jak to robić. Jeśli w specyfikacji znajdą się elementy niezbyt przydatne, to powiemy, dlaczego tak jest, i przejdziemy dalej; jeśli jakieś zagadnienie jest
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 15
16
Rozdział 1. Wprowadzenie
szczególnie godne uwagi, poświęcimy mu więcej czasu. Będziemy traktować XML jako narzędzie, a nie jako slogan reklamowy czy kolejną „zabawkę”. Pamiętając o tym, spróbujmy odpowiedzieć na pytanie, czym jest XML.
Co to jest XML? XML to Extensible Markup Language, czyli rozszerzalny język znaczników. Podobnie jak jego przodek, SGML, XML jest metajęzykiem służącym do definiowania innych języków. Jest jednak o wiele prostszy i bardziej praktyczny niż SGML. XML to język znaczników, w którym nie sprecyzowano ani zestawu znaczników, ani gramatyki języka. Zestaw znaczników (ang. tag set) w języku znaczników określa, jakie znaczniki mają znaczenie dla parsera języka. Na przykład, w HTML-u można używać znaczników ze ściśle określonego zestawu. Możemy wstawić znacznik
, ale nie możemy użyć (angielskie słowo table oznacza nie tylko tabelę, ale także stół). Pierwszy ze znaczników ma dla aplikacji wykorzystującej dane HTML specyficzne znaczenie, a drugi nie — większość przeglądarek po prostu go zignoruje, ale niektóre mogą zachować się nieprzewidywalnie. Wszystko to dlatego, że kiedy definiowano standard HTML, określono w nim konkretny zestaw znaczników. W każdej kolejnej wersji HTML-a dodawane są nowe znaczniki. Jeśli jednak znacznik nie jest zdefiniowany, to przetwarzanie dokumentu zawierającego taki znacznik może spowodować błąd. Gramatyka języka definiuje poprawne użycie jego znaczników. I znów weźmy HTML jako przykład. Do znacznika
można dodać szereg atrybutów, określających szerokość tabeli, kolor tła, wyrównanie itd. Nie możemy jednak wstawić np. atrybutu TYPE, bo nie pozwala na to właśnie gramatyka języka. XML nie definiuje ani znaczników, ani gramatyki. Ma więc nieograniczone możliwości rozbudowy, jest rozszerzalny — i stąd jego nazwa. Jeśli postanowimy korzystać ze znacznika
, a potem jeszcze w danych objętych tym znacznikiem zagnieździć kilka znaczników , to bez trudności możemy to zrobić. Jeśli chcielibyśmy zdefiniować atrybut określający typ krzesła, np. TYPE, też mamy taką możliwość. Możemy nawet wstawiać znaczniki o nazwach takich jak imiona naszych dzieci! Możliwości te zostały przedstawione w przykładzie 1.1.
Przykład 1.1. Przykładowy plik XML
Drewnosklep S.A.
2 doskonała
niebieski
3 średnia
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 16
Co to jest XML?
17
Jeśli Czytelnik nigdy nie widział pliku XML, a miał do czynienia tylko z HTML-em lub innymi językami znaczników, to przykład może się wydawać dość osobliwy. To dlatego, że znaczniki i gramatyka opisująca dane zostały w całości zmyślone. Żadna strona WWW ani specyfikacja nie definiuje znaczników
, czy poduszka (ale mogłoby tak być — w podobny sposób specyfikacja XHTML definiuje znaczniki HTML wewnątrz XML-a). Tu właśnie drzemie siła XML-a — umożliwia on definiowanie zawartości danych na różne sposoby i wymaga jedynie zgodności z ogólną strukturą języka. W dalszej części książki są przedstawione różnego rodzaju ograniczenia, ale na razie wystarczy pamiętać, że XML został stworzony po to, by zapewnić elastyczność formatowania danych. Ta elastyczność jest jedną z największych zalet XML-a, a jednocześnie jedną z jego największych wad. Dokumenty XML można przetwarzać na tak wiele różnych sposobów i w tak wielu różnych celach, że powstała bardzo duża liczba związanych z XML-em standardów opisujących tłumaczenie formatów danych i same formaty. Te dodatkowe skróty i ich nieustanne pojawianie się „w okolicach” XML-a często powoduje błędne rozumienie idei tego standardu. Kiedy ktoś mówi „XML”, to jest niemal pewne, że nie ma na myśli samego języka XML, tylko jedno z towarzyszących mu narzędzi. Niejednokrotnie zajmować się będziemy właśnie tymi narzędziami; trzeba jednak pamiętać, że najczęściej „XML” nie oznacza samego języka, lecz „XML i towarzyszące mu wspaniałe metody manipulowania danymi”. Skoro to rozróżnienie mamy już za sobą, możemy przejść do rozszyfrowania najbardziej popularnych skrótów związanych z XML-em. Skróty te są niezwykle istotne dla całej książki, a więc warto zostawić w tym miejscu zakładkę, aby móc w każdej chwili zajrzeć na te strony. Poniższe opisy powinny przybliżyć Czytelnikowi wzajemne związki między narzędziami związanymi z XML-em oraz wyjaśnić, czym jest XML. Nie będziemy tutaj omawiać mechanizmów publikacyjnych, aplikacji i narzędzi dla XML-a, bo są one przedstawione w dalszej części książki. Poniżej Czytelnik znajdzie informacje jedynie o specyfikacjach i zaleceniach. Większość z nich powstała z inicjatywy W3C (World Wide Web Consortium). Organizacja ta zajmuje się definiowaniem standardów XML i spełnia rolę podstawowej bazy informacji o tym standardzie — podobnie jak firma Sun jest podstawowym źródłem informacji o Javie i związanych z nią interfejsach API. Więcej informacji o W3C można znaleźć pod adresem http://www.w3.org.
XML XML to oczywiście punkt wyjścia wszystkich tych trzy- i czteroliterowych skrótów. Definiuje sam język i określa strukturę metadanych. XML sam w sobie ma niewielką wartość — to tylko struktura. Ale rozmaite technologie bazujące na tej strukturze dają programistom i administratorom zawartości niespotykaną do tej pory elastyczność w zarządzaniu i przesyłaniu danych. XML ma już status ukończonego zalecenia W3C, co oznacza, że nic się nie zmieni aż do opublikowania nowej wersji standardu. Pełna specyfikacja XML 1.0 znajduje się pod adresem http://www.w3.org/ TR/REC-xml/. Specyfikacja ta jest trudną lekturą nawet dla programistów dobrze znających XML, więc polecałbym raczej zapoznanie się z jej doskonałą wersją opatrzoną komentarzami, dostępną pod adresem http://www.xml.com. Temat ten będzie w dalszych rozdziałach omawiany szczegółowo, więc teraz wystarczy pamiętać o dwóch podstawowych koncepcjach koniecznych do zrozumienia istoty dokumentu XML. Pierwsza mówi, że dokument XML, aby był w jakikolwiek sposób przydatny i mógł zostać przetworzony, musi być poprawnie uformowany. Poprawnie uformowany dokument to taki, w którym każdemu znacznikowi otwierającemu odpowiada znacznik zamykający, w którym znaczniki nie są zagnieżdżone niezgodnie z regułami oraz którego składnia jest zgodna ze specyfikacją. Ale zaraz — czyż nie powiedzieliśmy wcześniej, że XML nie posiada żadnych reguł składniowych? Niezupełnie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 17
18
Rozdział 1. Wprowadzenie
Powiedzieliśmy, że nie ma reguł gramatycznych. Dany dokument może definiować własne znaczniki i atrybuty, ale wciąż musi być zgodny z regułami ogólnymi. Reguły te wykorzystywane są następnie przez aplikacje i parsery znające XML w celu odczytania danych z dokumentu i wykonania na nich pewnych czynności — np. znalezienia ceny krzesła czy utworzenia z danych dokumentu PDF. Zagadnienie to jest dokładniej omówione w rozdziale 2., Pisanie w XML-u. Druga istotna koncepcja związana z dokumentami XML mówi, że mogą one — ale nie muszą — być poprawne. Poprawny dokument to taki, który spełnia wymagania odpowiadającej mu definicji typu dokumentu DTD (o niej za chwilę). Mówiąc krótko, DTD definiuje gramatykę i zestaw znaczników na potrzeby określonego formatowania XML. Jeśli w dokumencie określono konkretną definicję DTD i jeśli jest on zgodny z tą definicją, to dokument taki określa się jako poprawny. Dokumenty XML mogą zostać także zawężone w ramach schematu — jest to nowy sposób narzucania formatu XML, który wyprze DTD. Dokument może być więc także zgodny ze schematem. W dalszej części książki zajmiemy się dokładniej przedstawionymi wyżej zagadnieniami. Najpierw jednak musimy poznać kilka skrótów i specyfikacji używanych wewnątrz dokumentu XML.
PI PI to instrukcja przetwarzania (ang. processing instruction) w dokumencie XML. Instrukcja przetwarzania nakazuje aplikacji wykonanie określonego zadania. Instrukcje PI, choć zajmują niewiele miejsca w specyfikacji XML, są na tyle ważne, że znalazły się wśród omawianych akronimów. PI wyróżnia się spośród innych danych w dokumencie XML tym, że oznacza polecenie przekazywane parserowi XML lub innemu programowi korzystającemu z dokumentu. W naszym przykładowym dokumencie 1.1 pierwszy wiersz, wskazujący wersję standardu XML, stanowi instrukcję przetwarzania. Informuje parsery, z której wersji standardu będziemy korzystać. Instrukcje przetwarzania mają postać . Wszystkie instrukcje PI, w których jako cel określono XML, należą do standardowego zestawu instrukcji określonego w ramach XML-a i powinny być rozpoznawane przez parsery. Nazywa się je często instrukcjami XML. Ale instrukcje PI mogą także zawierać informacje, które mają zostać wykorzystane przez aplikacje przechwytujące czynności parsera; w takim przypadku jako cel instrukcji przetwarzania można określić słowo kluczowe odpowiadające danej aplikacji (np. „cocoon”). Instrukcje przetwarzania nabierają dużego znaczenia, gdy dane XML wykorzystywane są w aplikacjach znających ten standard. Rozważmy aplikację, która przetwarza nasz przykładowy plik XML, a następnie tworzy reklamę mebli opartą na produktach wymienionych w dokumencie. Instrukcja przetwarzania może poinformować aplikację, że dany mebel jest na liście „zapotrzebowania” i że ma zostać przekazany do innej aplikacji, np. takiej, która wysyła zamówienia — zatem taki mebel ma nie pojawiać się w reklamie. Parser XML rozpozna instrukcje PI odwołujące się do celów zewnętrznych i przekaże je w niezmienionej postaci zewnętrznym aplikacjom.
DTD DTD to document type definition, czyli definicja typu dokumentu. DTD narzuca szereg ograniczeń na dokument (lub grupę dokumentów) XML. Definicja DTD sama w sobie nie jest specyfikacją, ale została zdefiniowana jako część specyfikacji XML. Deklaracja typu dokumentu wewnątrz dokumentu XML może sama zawierać ograniczenia odnośnie znaczników, ale może także odwoływać się do zewnętrznego dokumentu opisującego takie ograniczenia. Definicją typu dokumentu jest suma tych dwóch powyższych zestawów ograniczeń. DTD definiuje sposób, w jaki ma być skonstruowany dokument XML. Ponownie spójrzmy na przykład 1.1. Choć stworzyliśmy grupę własnych znaczników, to dokument taki jest bezużyteczny dla innej aplikacji, a nawet dla innego użytkownika, który nie potrafi zinterpretować naszych znaczników. Powstają wątpliwości — czy
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 18
Co to jest XML?
19
znacznik mówi nam, ile krzeseł jest na stanie? Czy atrybut drewno może zostać użyty w znaczniku ? Jeśli dokument ma zostać poprawnie przetworzony przez parser, na te wszystkie pytania trzeba znaleźć odpowiedzi. Dokument uważa się za poprawny, jeśli jest zgodny z ograniczeniami narzucanymi przez DTD odnośnie formatowania danych XML. Jest to szczególnie istotne, gdy zamierzamy przenosić dane pomiędzy aplikacjami — wymaga to uzgodnienia formatu i składni, aby dwa różne systemy mogły się porozumieć. Jak już wcześniej wspomniano, definicja DTD definiuje ograniczenia, mówiąc inaczej — zawęża konkretny dokument lub zestaw dokumentów XML. Programista lub autor zawartości tworzy także definicję DTD w postaci dodatkowego dokumentu, do którego odwołuje się z plików XML; może także zawrzeć ją w samym pliku XML — w każdym razie definicja nie ingeruje w żaden sposób w dokument XML. Tak naprawdę to właśnie DTD przyczynia się do przenośności danych XML. Może ona na przykład określać, że jedynymi poprawnymi wartościami atrybutu drewno są „klon”, „sosna”, „dąb” i „mahoń”. Dzięki temu parser potrafi określić, czy zawartość dokumentu jest do przyjęcia, i zapobiec ewentualnym błędom formatowania danych. Definicja DTD określa także kolejność zagnieżdżania znaczników. Może na przykład stanowić, że znacznik ma prawo być zagnieżdżany jedynie w znacznikach . Dzięki temu inna aplikacja, która otrzymuje nasz przykładowy plik XML, wie, jak przetworzyć i przeszukiwać otrzymane dane. Definicja DTD nie tylko przyczynia się do wysokiej elastyczności formatowania danych w XML-u, ale także umożliwia przetwarzanie i sprawdzanie poprawności danych w dowolnym systemie potrafiącym odnaleźć tę definicję.
Przestrzeń nazw Przestrzeń nazw to jedno z niewielu pojęć związanych z XML-em, dla których nie wymyślono akronimu. Nie ma takiej potrzeby — w tym przypadku nazwa dobrze odpowiada funkcji tego elementu. Przestrzeń nazw (ang. namespace) to odwzorowanie pomiędzy przedrostkiem elementu a identyfikatorem URI. Odwzorowanie to umożliwia zapobieżenie kolizjom przestrzeni nazw oraz określenie struktur danych, które pozwalają parserom zapobiec takim kolizjom. Przeanalizujmy przykład kolizji przestrzeni nazw. Załóżmy, że w dokumencie pomiędzy znacznikami i znajduje się znacznik . Ale w ramach definicji krzesła jest też znacznik , który przecież także może posiadać własny znacznik . Zauważmy również, że dokument może odwoływać się do innego dokumentu XML w celu pobrania informacji o prawach autorskich. W obu dokumentach powinny pojawić się znaczniki lub np. , ale jak rozpoznać, który znacznik odwołuje się do czego? Taka dwuznaczność to problem dla parsera XML. Czy znacznik ma być interpretowany w różny sposób zależnie od tego, w którym elemencie jest zagnieżdżony? Czy może autor zawartości pomyłkowo użył go w dwóch kontekstach? Bez informacji o przestrzeni nazw nie jest możliwe określenie, czy był to błąd w konstrukcji dokumentu XML, a jeśli było to działanie celowe — jak rozporządzić danymi kolidujących znaczników. Zalecenie opisujące przestrzenie nazw w XML-u definiuje mechanizm do kwalifikowania nazw. Mechanizm ten wykonuje swoje zadanie z użyciem identyfikatora URI, ale tego na razie nie musimy wiedzieć. Poprawne stosowanie znaczników w rodzaju opisywanego nie wymaga wpisywania niezbyt mądrych nazw w rodzaju czy . Zamiast tego przestrzeń nazw tworzona jest z wykorzystaniem przedrostka w postaci elementu XML — a więc na przykład: czy . Parser XML umie potem rozróżnić te przestrzenie i wcale nie trzeba tworzyć nowych nazw. Przestrzenie nazw są najczęściej używane w dokumentach XML, ale pojawiają się także w schematach i arkuszach sty-
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 19
20
Rozdział 1. Wprowadzenie
lów i w innych specyfikacjach powiązanych z XML-em. Zalecenie odnośnie przestrzeni nazw można znaleźć pod adresem http://www.w3.org/TR/REC-xml-names.
XSL i XSLT XSL to Extensible Stylesheet Language, czyli rozszerzalny język arkuszy stylów. Służy do przekształcania i tłumaczenia danych XML z jednego formatu XML na inny. Rozważmy przykład, w którym ten sam dokument XML musi zostać wyświetlony w formatach HTML, PDF i Postscript. Bez zastosowania XSL-a dokument XML musiałby być ręcznie skopiowany, a następnie przekształcony na jeden ze wspomnianych formatów. Mechanizm XSL umożliwia zdefiniowanie arkuszy stylów wykonujących to zadanie. Zamiast zmieniać dane pod kątem różnych reprezentacji, rozdzielamy dane (zawartość) od reprezentacji — właśnie za pomocą języka XSL. Jeśli dokument XML musi zostać odwzorowany w postaci innej reprezentacji, jest używany XSL. Wykonuje podobne zadanie jak program w Javie, który tłumaczy dane na format PDF lub HTML, ale zadanie to wykonuje z wykorzystaniem standardowego interfejsu. Tłumaczenie może dokonywać się za pomocą obiektów formatujących (ang. formatting objects) zawartych wewnątrz dokumentu XSL. Obiekty te mają postać specyficznych, nazwanych znaczników, pod które może zostać podstawiona zawartość składająca się na docelowy typ dokumentu. Typowym obiektem formatującym jest znacznik, który przez jakiś procesor wykorzystywany jest do przetworzenia dokumentu XML na PDF; w takim przypadku znacznik ten zostanie zastąpiony informacją specyficzną dla dokumentu PDF. Obiekty formatujące to specyficzne instrukcje XSL i choć zostaną pokrótce omówione, to jednak wykraczają poza tematykę niniejszej książki. Skoncentrujemy się natomiast na XSLT, całkowicie tekstowym procesie transformacyjnym. W wyniku działania procesu XSLT — transformacji rozszerzalnego języka arkuszy stylów (ang. Extensible Stylesheet Language Transformation) — tekstowy arkusz stylów XSL oraz tekstowy dokument XML są „łączone”, tworząc dane XML sformatowane zgodnie z arkuszem stylów XSL. Zagadnienie to przybliża przykład 1.2.
Przykład 1.2. Inny przykładowy plik XML
&OReillyCopyright;
wykonanie wyrażenia, gdy bieżącym węzłem jest element JavaXML:Ksiazka, spowoduje zwrócenie elementów JavaXML:Spis oraz JavaXML:Copyright. Pełna specyfikacja XPath jest dostępna pod adresem http://www.w3.org/TR/xpath.
XML Schema Schemat XML Schema to zamiennik definicji DTD o znacznie większych możliwościach. Służy do ograniczania (zawężania) dokumentów XML. Choć tylko pobieżnie omówiliśmy definicje DTD, to już można dostrzec dość poważne ograniczenia tego standardu: brak znajomości hierarchii, trudności w obsłudze konfliktów przestrzeni nazw i niemożność określenia dozwolonych relacji pomiędzy dokumentami XML. Ograniczenia te są ze wszech miar usprawiedliwione — skąd zespoły pracujące nad specyfikacją miały wiedzieć, że XML będzie wykorzystywany na tak wiele różnych sposobów? Jednak wady definicji DTD zaczęły dokuczać autorom i programistom korzystającym z XML-a. Najistotniejszą cechą XML Schema jest fakt, że umożliwia on zbliżenie definicji DTD do języka XML. To może wydawać się niejasne, ale przypomnijmy sobie, że każdy akronim, o którym ostatnio mówiliśmy, określany jest na podstawie standardu XML. Arkusze stylów, przestrzenie nazw i cała reszta korzystają z XML-a w celu zdefiniowania specyficznych zastosowań i właściwości tego języka. A definicje DTD są zupełnie inne — nie wyglądają jak XML, nie współdzielą hierarchicznej struktury XML-a i nawet nie reprezentują w ten sam sposób danych. Definicje DTD nie przystają więc zbytnio do świata XML, a ponieważ to właśnie one obecnie definiują, jak dokumenty XML mają wyglądać, łatwo się w tym wszystkim pogubić. Problem ten naprawia XML Schema — w standardzie tym definiowanie XML-a odbywa się za pomocą samego XML-a. Nie mówiliśmy jeszcze wiele o „definiowaniu danych opisujących dane”, ale także to potrafi XML Schema. Specyfikacja XML Schema pozwala w większym stopniu korzystać z konstrukcji tylko jednego języka; nie trzeba uciekać się do innych konstrukcji definicji DTD. Członkowie W3C i twórcy XML-a słusznie uznali, że przebudowa DTD nie przyniosłaby oczekiwanego rezultatu. Dlatego postanowiono stworzyć schematy XML Schema, poprawiając w ten sposób nieodłączne wady DTD oraz dodając nowe funkcje, odpowiadające współczesnym zastosowaniom
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 22
Co to jest XML?
23
XML-a. Więcej informacji o tym istotnym projekcie W3C można znaleźć pod adresami http://www. w3.org/TR/xmlschema-1 oraz http://www.w3.org/TR/xmlschema-2. Ciekawe wprowadzenie do XML Schema jest dostępne pod adresem http://www.w3.org/TR/xmlschema-0.
XQL... XQL to język zapytań, dzięki któremu w dokumentach XML można w prosty sposób odwoływać się do baz danych. XQL, choć jeszcze nie został formalnie przyjęty przez W3C, jest na tyle powszechny i przydatny, że faktycznie stanowi już popularną metodę uzyskiwania dostępu do baz danych z dokumentów XML. Struktura zapytania definiowana jest za pomocą pojęć XPath, a zestaw wynikowy — za pomocą standardowego XML-a i znaczników specyficznych dla XQL-a. Na przykład, poniższe wyrażenie XQL przeszukuje tablicę ksiazki i zwraca wszystkie rekordy, w których tytuł zawiera słowo „Java”. Dla każdego rekordu wyświetlany jest odpowiadający mu rekord w tablicy autorzy: //ksiazki[tytul contains "Java"] ( .//autorzy )
Zestaw wynikowy takiego zapytania może wyglądać następująco:
Najprawdopodobniej w miarę dojrzewania specyfikacji nastąpią w niej jakieś zmiany; na pewno jednak warto trzymać rękę na pulsie i interesować się XQL-em. Obecna propozycja standardu XQL znajduje się pod adresem http://metalab.unc.edu/xql/xql-proposal.html. Spotkała się ona z zainteresowaniem grupy W3C w styczniu 2000 roku, a obecne wymagania odnośnie języka zapytań XML można znaleźć pod adresem http://www.w3.org/TR/xmlquery-req.
... i cała reszta Mamy już za sobą pobieżne wprowadzenie do najważniejszych z omawianych w książce specyfikacji powiązanych z XML-em. Nie zostały omówione wszystkie liczące się akronimy, a jedynie te, które mają szczególne znaczenie przy omawianiu języka XML z punktu widzenia Javy. Pozostałe są wymienione poniżej, wraz z adresami odpowiednich zaleceń lub projektów roboczych: • Struktura Resource Description Framework (RDF): http://www.w3.org/TR/PR-rdf-schema/ • Język odsyłaczy XML (ang. XML Link Language — XLL): — XLink: http://www.w3.org/TR/xlink/ — XPointer: http://www.w3.org/TR/xptr/ • XHTML: http://www.w3.org/TR/xhtml-basic/ Kiedy Czytelnik będzie czytał tę książkę, prawdopodobnie powyższa lista będzie już nieaktualna — wciąż pojawiają się nowe rozwiązania w ramach XML-a. Nie wszystkie zostały szczegółowo omówione w tej książce, ale to wcale nie znaczy, że nie zasługują na uwagę. Nie są po prostu tak bardzo istotne przy omawianiu obsługi XML-a z poziomu Javy. Pełne zrozumienie XML-a na
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 23
24
Rozdział 1. Wprowadzenie
pewno wymaga opanowania zarówno tych technologii, które dokładniej opisujemy, jak i tych jedynie wspomnianych. Ale i tak może się zdarzyć, że w pewnym miejscu książki Czytelnik napotka standardy, które nie zostały omówione — wówczas takie zagadnienie zostanie dokładnie przedstawione.
Jak z tego korzystać? Wszystkie wspaniałe zalety XML-a nie na wiele się zdadzą, jeśli nie będzie narzędzi umożliwiających zastosowanie ich w naszym ulubionym środowisku programistycznym. Na szczęście, XML jest związany z Javą. Java posiada najpełniejszy wybór interfejsów API do obsługi XML-a bezpośrednio z kodu. Co prawda języki takie jak C, C++ i Perl nadrabiają zaległości w tym zakresie, ale to właśnie Java ustanawia standardy dotyczące sposobu obsługi XML-a z poziomu aplikacji. Z punktu widzenia aplikacji, cykl działania dokumentu XML dzieli się na dwa etapy (rysunek 1.1): przetwarzanie dokumentu i analizowanie danych w nim zawartych.
Rysunek 1.1. Cykl dzialania dokumentu XML z punktu widzenia aplikacji Jako programiści Javy, mamy dostęp do bardzo prostych sposobów wykonywania powyższych, a także wielu innych czynności.
SAX SAX to Simple API for XML, czyli prosty interfejs API do obsługi XML-a. Do przetwarzania danych XML — czyli procesu odczytywania dokumentu i rozbijania danych na odpowiednie elementy — udostępnia strukturę opartą na obsłudze zdarzeń. Na każdym etapie definiowane są zdarzenia, które mogą wystąpić. Na przykład SAX definiuje interfejs org.xml.sax.ContentHandler, który z kolei definiuje metody, takie jak startDocument() i endElement(). Za pomocą takiego interfejsu można uzyskać pełną kontrolę nad odpowiednimi etapami procesu przetwarzania dokumentu XML. Podobny interfejs stworzono do obsługi błędów i konstrukcji leksykalnych. Definiowany jest zestaw błędów i ostrzeżeń, co umożliwia obsługę różnych sytuacji mogących wystąpić w czasie przetwarzania dokumentu XML — np. napotkanie dokumentu niepoprawnego lub źle sformatowanego. Można dodawać nowe zachowania, co umożliwia obsługę zadań związanych z bardzo specyficznymi aplikacjami — a wszystko to w ramach standardowego interfejsu XML. Dokumentacja i inne informacje na temat interfejsu SAX znajdują się pod adresem http://www.megginson.com/SAX.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 24
Jak z tego korzystać?
25
Przed przystąpieniem do omawiania kolejnych zagadnień należy koniecznie wyjaśnić pewne nieporozumienie związane z interfejsem SAX. Otóż SAX często jestb uważany za parser XML-a. Nawet tutaj piszemy, że SAX umożliwia przetwarzanie danych XML. Ale tak naprawdę SAX udostępnia tylko strukturę dla parserów i definiuje zdarzenia procesu przetwarzania podlegające monitorowaniu. Żeby jakiekolwiek przetwarzanie mogło nastąpić, konieczne jest jeszcze zastosowanie zewnętrznego parsera. Powstało wiele świetnych parserów napisanych w Javie, takich jak Project X Suna, Xerces z Apache Software Foundation, XML Parser firmy Oracle i XML4J IBM-a. Wszystkie one podłączane są do interfejsu SAX i umożliwiają uzyskanie przetworzonych danych XML. Interfejs SAX udostępnia sposób na przetwarzanie dokumentu, ale sam nie jest parserem.
DOM DOM to interfejs API dla obiektowego modelu dokumentu (ang. Document Object Model). SAX umożliwia jedynie dostęp do danych w dokumencie XML, natomiast DOM pozwala na manipulowanie danymi. W ramach interfejsu DOM dokument XML reprezentowany jest jako struktura drzewiasta. Ponieważ taki sposób reprezentowania znany jest już od dawna, nie ma trudności z przeszukiwaniem i przetwarzaniem tego rodzaju struktur z poziomu języków oprogramowania — a więc także z poziomu Javy. W interfejsie DOM cały dokument XML wczytywany jest do pamięci, a wszystkie dane są składowane jako węzły, a więc bardzo szybko uzyskać można dostęp do poszczególnych części dokumentów — wszystko jest w pamięci dopóty, dopóki istnieje drzewo DOM. Poszczególne węzły odpowiadają konkretnym danym pobranym z oryginalnego dokumentu. DOM ma jednak pewną poważną wadę. Ponieważ cały dokument wczytywany jest do pamięci, zasoby szybko ulegają wyczerpaniu, często powodując spowolnienie działania aplikacji, a nawet uniemożliwienie jej poprawnego działania. Im większy i bardziej złożony jest dokument, tym bardziej spada wydajność. Należy o tym pamiętać — DOM to użyteczny i popularny, ale nie jedyny sposób manipulacji danymi XML. Interfejsowi temu poświęcimy nieco czasu; napiszemy także kod służący do manipulacji danymi wprost z interfejsu SAX. Charakterystyka aplikacji zadecyduje o tym, które rozwiązanie okaże się lepsze w danym projekcie programistycznym. Zalecenie W3C odnośnie interfejsu DOM znajduje się pod adresem http://www.w3.org/DOM.
JAXP JAXP to interfejs Javy do przetwarzania XML-a (ang. Java API for XML Parsing) firmy Sun. Interfejs ten, będący całkiem nowym narzędziem w arsenale programisty XML-a, ma na celu nadanie spójności interfejsów SAX i DOM. Nie jest rozwiązaniem konkurencyjnym w stosunku do nich, nie został też stworzony jako ich następca — JAXP oferuje uproszczone metody mające na celu ułatwienie korzystania z interfejsów Javy do obsługi XML-a. Jest zgodny ze specyfikacjami SAX i DOM, a także ze wspomnianym wcześniej zaleceniem dotyczącym przestrzeni nazw. JAXP nie definiuje zachowania interfejsów SAX ani DOM, ale zapewnia standardową warstwę dostępu dla wszystkich parserów XML-a z poziomu Javy. Oczekuje się, że JAXP będzie ewoluował w miarę modyfikacji interfejsów SAX i DOM. Można także założyć, że ostatecznie zajmie on miejsce wśród innych specyfikacji firmy Sun, jako że zarówno mechanizm serwletów Tomcat, jak i specyfikacja EJB 1.1 wymagają plików konfiguracyjnych i wdrożeniowych sformatowanych w XML-u. Choć specyfikacje J2EE® 1.3 oraz J2SE® 1.4 nie mówią jawnie o JAXP, to oczekuje się, że także w tych narzędziach zostanie zintegrowana obsługa JAXP. Pełna specyfikacja JAXP znajduje się na stronie http://java.sun.com/xml. Te trzy interfejsy składają się na warsztat programistów Javy mających do czynienia z formatem XML. Nie jest to zatwierdzone formalnie, ale właśnie ta trójka oferuje mechanizm pobierania
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 25
26
Rozdział 1. Wprowadzenie
i manipulowania danymi z poziomu zwykłego kodu w Javie. Interfejsy te będą nam potrzebne w całej książce; dowiemy się wszystkiego o klasach, które każdy z nich udostępnia.
Dlaczego warto korzystać z technologii XML? W powyższych podrozdziałach zostały omówione najważniejsze skróty opisujące technologie związane z XML-em. Być może Czytelnik zorientował się już, że XML to coś więcej niż kolejny sposób tworzenia warstwy prezentacji. Ale nie jest jeszcze pewien, w którym miejscu tworzonych aplikacji standard ten można zastosować. Nie udałoby nam się jeszcze przekonać pracodawcy, że powinniśmy poświęcić więcej czasu na poznawanie XML-a — po prostu nie umielibyśmy udowodnić mu, że XML może przyczynić się do stworzenia lepszej aplikacji. Chcielibyśmy pewnie nawet wypróbować to lub tamto narzędzie do pracy na dokumentach XML, ale nie wiemy, od czego zacząć. Jeśli Czytelnik ma teraz takie właśnie odczucie — zainteresowanie nową technologią i trudność zdecydowania, co robić dalej — to warto czytać dalej! W tej części poznamy relacje między XML-em a rzeczywistymi aplikacjami i uzasadnimy potrzebę korzystania z tego standardu. Najpierw zostanie omówione zastosowanie języka XML we współczesnych aplikacjach. Następnie autor przedstawi wsparcie oferowane dla XML-a i powiązanych z nim technologii — wszystko to w świetle aplikacji w Javie. Java oferuje cały wachlarz parserów, narzędzi przekształcających, mechanizmów publikacyjnych i struktur opracowanych specjalnie pod kątem XML-a. W zakończeniu zostaną omówione kierunki rozwoju XML-a. Te wiadomości będą argumentami służącymi do przekonania przełożonego (i jego przełożonych), jak bardzo XML liczy się teraz na rynku.
Java i XML — małżeństwo idealne Wiemy już, że XML to naprawdę przydatna technologia i że zdobywa ogromną popularność, jednak Czytelnik tej książki może sobie zadawać pytanie, dlaczego traktuje ona o Javie i XML-u, a nie tylko o tym ostatnim. Otóż Java idealnie pasuje do XML-a z jednego zasadniczego powodu: Java to przenośny kod, XML to przenośne dane. Oddzielnie obie te technologie są naprawdę bardzo dobre, ale mają ograniczenia. Java wymaga od programisty tworzenia formatów danych do przesyłania przez sieć i do prezentacji oraz korzystania z technologii typu Java Server Pages™ (JSP), które nie zapewniają faktycznej separacji warstw zawartości i prezentacji. XML to po prostu metadane i bez parserów czy procesorów XSL jest to „oprogramowanie - widmo”. Kiedy Java i XML zostaną połączone, wszystkie te ograniczenia programistyczne znikają. Kiedy napiszemy program w Javie, to mamy pewność, że skompilowany kod bajtowy będzie mógł zostać uruchomiony na dowolnym sprzęcie i w dowolnym systemie operacyjnym wyposażonym w wirtualną maszynę Javy (JVM). Jeśli do tego dodamy możliwość reprezentacji danych wejściowych i wyjściowych aplikacji z wykorzystaniem niezależnej od systemu, standardowej warstwy danych, to otrzymujemy całkowitą przenośność informacji. Aplikacja jest przenośna i porozumiewa się z dowolną inną aplikacją za pomocą tych samych (szeroko przyjętych) standardów. Mało tego, wspomnieliśmy już także, że Java udostępnia szerszy wachlarz interfejsów, parserów, procesorów, struktur publikacji i narzędzi dla XML-a niż jakikolwiek inny język. Pamiętając o tym wszystkim, zobaczymy, jak te dwie technologie współpracują dziś i jak ta współpraca będzie przebiegała w latach następnych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 26
Dlaczego warto korzystać z technologii XML?
27
XML dzisiaj Wśród wielu programistów i pracowników firm branży technologicznej panuje przekonanie, że XML to rzeczywiście nowoczesna technologia, ale że nie jest jeszcze gotowa do zastosowania w aplikacjach krytycznych, od których zależy funkcjonowanie całej firmy. To mylna opinia. XML i spokrewnione technologie, o których wspomnieliśmy, trwale zadomowiły się w aplikacjach — przyjęły się znacznie szybciej niż zaprezentowana kilka lat temu Java. Najprawdopodobniej to właśnie XML pobił rekord Javy w zdobywaniu rynku informatycznego. My, programiści, musimy sobie zdawać sprawę z faktu, że te dwie technologie uzupełniają się, a nie zwalczają. Dane i aplikacje nie mogą być już bardziej przenośne niż te napisane w Javie i XML-u. Obydwie technologie są już teraz bardzo często stosowane.
XML w prezentacji danych XML najczęściej stosowany jest do rozdzielania zawartości od sposobu jej prezentacji. Zawartością (ang. content) nazywamy dane, które mają zostać wyświetlone u klienta, zaś prezentacją (ang. presentation) samo formatowanie tych danych. Na przykład, nazwa i adres pracownika działu administracji to zawartość, a sformatowana strona w HTML-u, z grafiką i emblematem firmy, to prezentacja. Zasadnicza różnica polega na tym, że zawartość jest uniwersalna względem aplikacji i może zostać poddana dowolnemu formatowaniu; prezentacja natomiast związana jest z konkretnym typem klienta (przeglądarką WWW, telefonem z obsługą Internetu, aplikacją Javy) oraz z funkcjami udostępnianymi przez klienta (HTML 4.0, język WML, Java™ Swing). Za zawartość odpowiedzialny jest wtedy XML, zaś za prezentację odpowiadającą poszczególnym klientom — XSL i XSLT. Jednym z największych wyzwań stojących przed współczesnymi aplikacjami, szczególnie aplikacjami WWW, jest różnorodność klientów z nich korzystających. Jeszcze dziesięć lat temu istniały niemal wyłącznie klienty pełne (ang. thick clients), z zainstalowanym oprogramowaniem pozwalającym na korzystanie z aplikacji. Trzy lata temu klienty aplikacji miały niemal zawsze postać przeglądarek internetowych „rozumiejących” HTML. Obecnie korzysta się z przeglądarek na najrozmaitszych platformach systemowych, popularne stały się telefony komórkowe z obsługą bezprzewodowego języka znaczników WML (ang. Wireless Markup Language) oraz asystenty kieszonkowe („organizery”) z obsługą podzbioru HTML-a. Taka różnorodność powoduje, że nieraz tworzy się niezliczone wersje tej samej aplikacji — po jednej wersji dla każdego typu klienta — a i tak nie wszystkie klienty są poprawnie obsługiwane. Aplikacja nie musi koniecznie obsługiwać np. telefonu komórkowego, ale przecież jeśli już odbiorcy lub pracownicy firmy wyposażeni są w takie urządzenia, to czy nie warto z nich skorzystać? Może i kieszonkowy asystent nie umożliwia wykonywania wszystkich operacji, jakie udostępnia zwyczajna przeglądarka WWW, ale czy osoby często przebywające w podróży nie docenią możliwości przynajmniej podstawowej obsługi swoich spraw w naszej firmie za ich pośrednictwem? Twórcy aplikacji i osoby zarządzające firmami mają dylemat — czy tak jak dawniej oferować wszystkie funkcje tylko wybranym klientom, czy, zgodnie z nowymi tendencjami, oferować jedynie ograniczone funkcje, ale wszystkim klientom? Odpowiedzią jest XML. Jak powiedzieliśmy wcześniej, XML nie jest technologią prezentacyjną. Jedynie może być użyty do wygenerowania warstwy prezentacyjnej. Jeśli Czytelnik nie dostrzega różnicy, może warto odwołać się do HTML-a jako technologii prezentacyjnej. HTML ma postać języka znaczników dobranych tak, że umożliwiają graficzne rozplanowanie elementów na stronie wyświetlanej przez przeglądarkę WWW. Ale przecież zastosowanie HTML-a nie jest dobrym sposobem reprezentacji danych. Dokumentu w tym formacie ani łatwo się nie przeszukuje, ani nie przetwarza. Format został zdefiniowany dość swobodnie, a przynajmniej połowę pliku zajmują znaczniki prezentacji — właściwe dane to tylko niewielki procent dokumentu. XML jest zupełnie inny. Ten język znaczników ukierunkowany jest na dane, a nie na prezentację. Niemal całość dokumentu XML to
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 27
28
Rozdział 1. Wprowadzenie
dane i opis ich struktury. Do danych nie odwołują się jedynie instrukcje dla parsera XML lub aplikacji przechwytujących. Przeszukiwanie dokumentu XML i jego przetwarzanie z wykorzystaniem interfejsów API i narzędzi jest proste, dzięki ściśle określonej strukturze narzuconej przez DTD lub schemat. XML jest całkowicie odłączony od warstwy prezentacji. Może jednak zostać wykorzystany do tworzenia warstwy prezentacyjnej poprzez zastosowanie spokrewnionych technologii XSL i XSLT. W ramach XSL tworzona jest definicja i konstrukcje odpowiedzialne za prezentacje, a także instrukcje informujące, jak te konstrukcje mają się do danych w dokumencie XML. XSLT umożliwia wyświetlenie oryginalnego dokumentu XML u klienta na różne sposoby, także poprzez bardzo złożony format HTML. Przez cały ten czas bazowy dokument XML pozostaje odseparowany od warstwy prezentacji. W każdej chwili może w równie prosty sposób zostać dostosowany do zupełnie innego stylu prezentacji, np. interfejsu użytkownika Swing, bez konieczności wprowadzania jakichkolwiek zmian w oryginalnych danych. Być może najpotężniejszą funkcją oferowaną przez XML i XSL na potrzeby prezentacji jest możliwość tworzenia wielu arkuszy stylów odpowiadających dokumentowi XML albo narzucania z zewnątrz takich arkuszy. Ta możliwość zwiększa elastyczność prezentacji: nie tylko można korzystać z tego samego dokumentu XML na potrzeby wielu prezentacji, ale struktura publikacji dokonująca transformacji może sama określić, od jakiego klienta nadeszło żądanie dokumentu, i wybrać odpowiedni arkusz stylu. Nie istnieje standardowy sposób przeprowadzania tego procesu nie ma też standardowych kodów odpowiadających różnym klientom, ale struktura publikacji XML pozwala na przeprowadzanie takich czynności w sposób dynamiczny. Proces tworzenia wielu arkuszy stylów XSL w ramach dokumentu XML nie jest zależny od produktu żadnej firmy, więc jedyne, co należy zrobić w dokumencie XML w celu wykorzystania go w strukturze publikacji, to wprowadzenie jednej czy dwóch instrukcji przetwarzających. Instrukcje takie, jeśli nie są obsługiwane ze strony aplikacji, zostają zignorowane, a więc oznakowane w ten sposób dane pozostają całkowicie przenośne i w stu procentach zgodne ze standardem XML.
XML w komunikacji Ten sam dokument XML może zostać nie tylko przekształcony, jak to zostało opisane wyżej, możliwe jest również przesłanie danych, które zawiera, do innej aplikacji. Wdrożenie takiej komunikacji nie sprawia żadnych trudności, ponieważ dane XML nie są związane z żadnym typem klienta — nie są nawet wykorzystywane bezpośrednio przez klienta. W szybki sposób uzyskać można również prostą reprezentację danych, łatwą do przesłania w sieci. To właśnie ten aspekt XML-a — łatwość przesyłania danych — jest chyba najbardziej niedoceniany. Aby zrozumieć, jak dużo daje możliwość komunikacji za pośrednictwem standardu XML, trzeba najpierw rozszerzyć swój sposób postrzegania aplikacji-klienta. Kiedy omawialiśmy prezentację, w typowy sposób założyliśmy, że klient to użytkownik uzyskujący dostęp do części aplikacji. Ale w dzisiejszych czasach jest to duże uproszczenie. Klient to tak naprawdę cokolwiek (tak, cokolwiek!), co uzyskuje dostęp do danych lub usług aplikacji. Klienty to np. komputery lub telefony komórkowe użytkowników; klienty to inne aplikacje, systemy składowania danych w rodzaju baz danych lub usług katalogowych; klientem może być nawet aplikacja łącząca się sama z sobą. Tak szerokie pojęcie klienta pozwala zrozumieć, jak duże znaczenie może mieć XML. Spróbujmy najpierw podzielić klienty na dwie grupy: klienty wymagające warstwy prezentacji i te, które warstwy takiej nie wymagają. Nie zawsze łatwo przeprowadzić taki podział. Na pewno formaty HTML i WML (Wireless Markup Language) to prezentacja, ale co zrobić, jeśli dane muszą zostać sformatowane jedynie nieznacznie na potrzeby innej aplikacji — np. poprzez odfiltrowanie danych zabezpieczonych lub zmianę nazw elementów? Tak naprawdę rzadko mamy do czynienia z klientem, który nie wymaga jakiegoś konkretnego formatowania danych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 28
Dlaczego warto korzystać z technologii XML?
29
Powyższa próba kategoryzacji powinna przekonać Czytelnika, że dane niemal zawsze podlegają transformacji, często wielokrotnej. Rozważmy dokument XML, który został przekonwertowany na format użyteczny dla innej aplikacji za pomocą arkusza XSL (rysunek 2.2). Plik wynikowy jest wciąż dokumentem XML. Taka aplikacja może następnie wykorzystać dane do stworzenia nowego zestawu wynikowego i utworzenia nowego dokumentu XML. Nowe informacje mają zostać przekazane ponownie pierwszej aplikacji, a więc nowy dokument XML jest znów przekształcany na oryginalny format — choć teraz zawiera inne dane! To bardzo typowy scenariusz.
Rysunek 1.2. Transformacje XML/XSL pomiędzy aplikacjami To właśnie ten cykliczny proces przetwarzania dokumentu i nieustannego tworzenia nowej postaci wynikowej XML czyni z XML-a tak potężne narzędzie komunikacyjne. Ten sam zestaw zasad wykorzystywany jest na każdym etapie procesu. Zawsze rozpoczyna się od dokumentu XML, przekształca zgodnie z jednym lub więcej arkuszami stylów XSL i uzyskuje się dokument XML, który wciąż jest użyteczny dla tych samych narzędzi, które stworzyły dokument oryginalny. Zauważmy także, że XML to dane reprezentowane w sposób czysto tekstowy. Ponieważ reprezentacja tekstowa nie wymaga dużych zasobów i łatwo wykonać na niej serializację danych, zatem przesyłanie tego typu danych w sieci jest łatwiejsze. Niektóre formaty binarne można przesyłać bardzo wydajnie, a jednak tekstowy XML niemal zawsze okazuje się szybszym sposobem komunikacji.
XML-RPC Specyfikacja, w której skupiono się właśnie na wykorzystaniu XML-a w komunikacji, to XML-RPC. Opisuje ona komunikację nie tyle pomiędzy aplikacjami, co pomiędzy poszczególnymi elementami aplikacji albo współdzielonymi usługami działającymi pomiędzy aplikacjami. RPC to skrót utworzony od słów Remote Procedure Calls (zdalne wywoływanie procedur). Jest jednym z najważniejszych poprzedników technologii zdalnego wywoływania metod RMI (ang. Remote Method Invocation). Za pomocą RPC możliwe jest wywoływanie procedur poprzez sieć i — także poprzez sieć — otrzymywanie odpowiedzi. Należy zauważyć, że to rozwiązanie jest zasadniczo różne od RMI, technologii, która pozwala klientowi uruchamiać metody na danym obiekcie za pośrednictwem „namiastek” metod (ang. stub) i szkieletów ładowanych poprzez sieć. Główna różnica polega na tym, że wywołania RPC powodują wygenerowanie odpowiedzi zdalnie; odpowiedź taka zwracana jest później poprzez sieć, a klient nigdy nie komunikuje się bezpośrednio z obiektem
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 29
30
Rozdział 1. Wprowadzenie
zdalnym (żąda wywołania metody za pomocą interfejsów RPC). Natomiast technologia RMI umożliwia klientowi bezpośrednią interakcję z obiektem zdalnym i nie ma żadnego „pośredniczenia” w żądaniach. Bardziej szczegółowy opis zasady działania technologii XML-RPC można znaleźć pod adresem http://www.xml-rpc.com. Technologia XML-RPC stała się bardzo użytecznym sposobem odwoływania się do zdalnych usług. Z powodu trudności stworzenia standardowego modelu żądanie-odpowiedź, technologia RPC niemal zupełnie przestała być używana w programach w Javie — zastąpiono ją technologią RMI. Istnieją jednak sytuacje, w których wysyłanie i odbieranie danych tekstowych jest wydajniejsze niż ładowanie zdalnych „namiastek” i szkieletów. RPC obciążony jest historycznym problemem polegającym na tym, że zawsze starano się reprezentować złożone obiekty wyłącznie tekstowo (zarówno żądania, jak i odpowiedzi). Problem ten rozwiązano w XML-u i RPC znów okazuje się dobrym sposobem komunikacji systemów zdalnych. Dzięki standardowi umożliwiającemu reprezentowanie dowolnego typu danych za pomocą dokumentów tekstowych mechanizm XML-RPC potrafi odwzorowywać parametry egzemplarza (ang. instance) obiektu na elementy XML i w prosty sposób dekodować ten „graf” obiektu na serwerze. Uzyskana odpowiedź może znowu zostać przekształcona na „graf” XML-a i zwrócona klientowi (rysunek 1.3). Więcej informacji o mechanizmie XML-RPC można znaleźć w rozdziale 10., XML-RPC.
Rysunek 1.3. Komunikacja i powiadamianie w standardzie XML-RPC
Firma-firma Ostatni ze sposobów zastosowania XML-a w celach komunikacyjnych tak naprawdę nie stanowi oddzielnej metody czy specyfikacji; ponieważ jednak pojęcie komunikacji i handlu typu firma-firma (ang. business-to-business) jest ostatnio bardzo popularne, należy je objaśnić. Komunikacja firma-firma oznacza nie tyle porozumiewanie się dwóch różnych aplikacji, co komunikację pomiędzy przedsiębiorstwami, a nawet całymi branżami. XML jest w takich przypadkach naprawdę pomocnym narzędziem — zapewnia komunikację pomiędzy systemami zamkniętymi, umożliwiając wdrożenie usług, na które kiedyś mogły sobie pozwolić jedynie największe firmy. Przeanalizujmy to zagadnienie na przykładzie. Załóżmy, że niewielki, lokalny operator telekomunikacyjny sprzedaje swojemu klientowi linię do przesyłania danych, np. DSL lub T1. Pociąga to za sobą szereg procesów (rysunek 1.4). Trzeba złożyć zamówienie u dostawcy linii, należy skonfigurować router.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 30
Dlaczego warto korzystać z technologii XML?
31
O ustawieniach routera trzeba poinformować usługodawcę internetowego (ISP). Następnie przeprowadzana jest faktyczna instalacja, do wykonania której może zostać wynajęta — np. na zasadzie outsourcingu — jeszcze inna firma. W tej względnie prostej operacji sprzedaży łącza sieciowego biorą udział aż trzy firmy! Jeśli do tego dodamy techników zatrudnionych przez producenta routera, firmę telekomunikacyjną świadczącą inne usługi klientowi czy NASK zajmujący się rejestracją domeny, to cały proces zaczyna urastać do sporego problemu. Na szczęście proces ten można uprościć, właśnie za pomocą XML-a (rysunek 1.5). Wyobraźmy sobie, że pierwotne zamówienie na założenie łącza zostaje wprowadzone do systemu, który konwertuje je na dokument XML. Dokument ten jest przetwarzany (za pomocą XSL-a) na format, który można przesłać dostawcy linii. Dostawca linii dodaje do tego dokumentu własne informacje i przekształca go na nowy dokument XML, który zwracany jest naszemu lokalnemu operatorowi. Ten nowy dokument przekazywany jest firmie instalującej z dodatkowymi informacjami o tym, gdzie znajduje się firma klienta. Po instalacji firma instalująca dodaje do dokumentu notatkę informującą o powodzeniu lub niepowodzeniu instalacji i po przekształceniu (znów za pomocą XSL-a) dokument jest oddawany do głównej aplikacji u operatora. Elegancja tego rozwiązania polega na tym, że zamiast kilku systemów firmowych, z których każdy ma swój sposób formatowania, na każdym etapie wykorzystywany jest ten sam interfejs XML — bez względu na rodzaj aplikacji, systemu czy firmy.
Rysunek 1.4. Proces instalowania łącza sieciowego z wykorzystaniem systemów firmowych
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 31
32
Rozdział 1. Wprowadzenie
XML w konfiguracji Jeszcze jedno istotne zastosowanie standardu XML w aplikacjach i technologiach związanych z Javą ma miejsce na poziomie serwera aplikacji. Specyfikacja Enterprise JavaBeans (EJB) 1.1 wymaga, aby deskryptory wdrożeniowe dla programów JavaBean (definiujące sposób działania i inne parametry związane z EJB) oparte były na XML-u. Wcześniej w tym celu używano serializowanych deskryptorów wdrożeniowych. W środowisku programistów EJB tę zmianę przyjęto z zadowoleniem, ponieważ deskryptory wdrożeniowe nie muszą być już tworzone zgodnie z wymogami rozwiązań firmowych. Ponieważ deskryptory wdrożeniowe mają być teraz zgodne z wcześniej zdefiniowaną definicją DTD, wszyscy producenci wykorzystują te same deskryptory. To zaś przyczynia się do zwiększenia przenośności kodu EJB.
Rysunek 1.5. Proces instalowania łącza sieciowego z wykorzystaniem danych w standardzie XML Standard XML wykorzystywany jest także w konfiguracji interfejsu API serwletów (wersja 2.2). Plik XML opisuje konfigurację samego mechanizmu serwletów (określa parametry złącza, początkowy kontekst serwleta i inne aspekty specyficzne dla mechanizmu). Pliki konfiguracyjne XML wykorzystywane są także do konfigurowania indywidualnych serwletów — umożliwiają przekazywanie argumentów początkowych, konfigurację wielu nazw serwleta oraz dopasowywanie adresów URL w specyficznych kontekstach serwletów. Chociaż zarówno specyfikacja EJB 1.1, jak i mechanizm serwletów Tomcat są nowymi elementami w krajobrazie języka Java, to zastosowanie XML-a jako podstawy konfiguracji świadczy o tym, że firma Sun przyjęła strategię wykorzystywania standardu XML w tego typu zastosowaniach. W miarę wzrostu popularności parserów XML, pliki konfiguracyjne w XML-u będą stosowane coraz częściej, i to nie tylko w serwerach opartych na Javie — również w np. serwerach HTTP czy bazach danych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 32
Dlaczego warto korzystać z technologii XML?
33
Obsługa XML-a W drugiej połowie 1999 roku standard XML przeżył rozkwit. Pojawiły się — a w chwili obecnej zyskały już stabilność i wysoką wydajność — parsery XML, procesory XSLT, struktury publikacji, edytory XML i zintegrowane środowiska programistyczne IDE, a także całe bogactwo narzędzi pokrewnych. Choć tematem tej książki jest interfejs API Javy do manipulacji danymi XML, to jednak parsery, procesory i inne elementy niewątpliwie stanowią część procesu przetwarzania XML-a, a więc poświęcone im będzie należne miejsce. Ponieważ technologia XML zmienia się niezwykle szybko, nie będziemy tutaj mówili o konkretnych wersjach — w czasie pojawienia się tej książki na rynku będą już zapewne nowsze. Ponadto jest bardzo możliwe, że wtedy dostępne będą nowe narzędzia. Jeśli Czytelnik nie znajdzie w książce informacji o obsłudze XML-a z poziomu określonego narzędzia, powinien uzyskać je od producenta oprogramowania.
Parsery Parser stanowi jedną z najważniejszych warstw aplikacji obsługującej XML. Element ten odpowiedzialny jest za niezwykle istotne zadanie analizowania dokumentu. Sprawdza, czy jest poprawnie sformatowany, czy umieszczono w nim odwołanie do odpowiedniej definicji DTD lub schematu; może także sprawdzać poprawność składni. Po takim przetworzeniu zazwyczaj uzyskiwana jest struktura danych — w naszym przypadku przeniesiona na grunt Javy — którą łatwo potem manipulować poprzez inne narzędzia XML lub interfejs API Javy. Nie będziemy teraz szczegółowo opisywać takich struktur, ponieważ są one przedstawione w kolejnych rozdziałach. Tymczasem wystarczy pamiętać, że parser to jeden z najważniejszych składników mechanizmu przetwarzania dokumentu XML. Wybór parsera XML nie jest zadaniem prostym. Nie obowiazują tutaj sztywne zasady, ale zazwyczaj brane są pod uwagę dwa kryteria. Pierwsze z nich to szybkość parsera. W miarę coraz częstszego wykorzystywania dokumentów XML i zwiększania ich złożoności szybkość parsera zaczyna mieć istotny wpływ na ogólną wydajność aplikacji. Drugie kryterium to zgodność ze specyfikacją XML. Ponieważ to właśnie wydajność jest często ważniejsza niż niektóre rzadko wykorzystywane cechy XML-a, niektóre parsery nie są w stu procentach zgodne ze specyfikacją XML. Użytkownik musi więc wypośrodkować pomiędzy tymi dwoma kryteriami, biorąc pod uwagę konkretne zastosowanie. Ponadto niektóre parsery potrafią sprawdzać poprawność składni XML na podstawie definicji DTD, a inne nie. Jeśli tego wymaga dana aplikacja, musimy skorzystać z parsera posiadającego taką umiejętność. Poniżej przedstawiony jest spis najpopularniejszych parserów XML. Nie zamieszczono tutaj informacji, czy dany parser wyposażony jest w funkcję sprawdzania poprawności składni, ponieważ w niektórych przypadkach funkcja taka jest właśnie dodawana. Nie przedstawiono tutaj także oceny tych parserów, ale informacje przedstawione na wymienionych stronach WWW powinny wystarczająco ułatwić wybór: • Apache Xerces, http://xml.apache.org • IBM XML4J, http://alphaworks.ibm.com/tech/xml4j • James Clark's XP, http://www.jclark.com/xml/xp • OpenXML, http://www.openxml.org • Oracle XML Parser, http://technet.oracle.com/tech/xml • Sun Microsystems Project X, http://java.sun.com/products/xml
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 33
34
Rozdział 1. Wprowadzenie
• Tim Bray's Lark and Larval, http://www.textuality.com/Lark • Grupa W3C poinformowała, że zamierza opublikować parser sprawdzający poprawność na podstawie schematu. Parser będzie oprogramowaniem typu open source.
Na tej liście celowo nie umieszczono parsera Microsoftu. Wygląda na to, że firma ta nie zamierza teraz ani w przyszłości utrzymywać zgodności ze standardami W3C. Microsoft najwyraźniej opracowuje własną wersję XML-a. Ileż to już razy przerabialiśmy... W każdym razie trzeba mieć się na baczności, gdy sytuacja zmusi nas do wykorzystania parsera Microsoftu, MSXML.
Procesory Po przetworzeniu dokumentu XML niemal zawsze następuje jego przekształcenie (transformacja). Przekształcenie to, jak już wspomnieliśmy, wykonywane jest za pomocą XSLT. Podobnie jak w przetwarzaniu, również na tym etapie obróbki dokumentu XML możemy wybierać spośród wielu narzędzi. Znów dwoma podstawowymi kryteriami wyboru są szybkość przekształcania i zgodność ze specyfikacjami XSL i XSLT. W czasie pisnia tej książki standard XSL zyskał status ukończonego zalecenia W3C, a więc obsługa konstrukcji i opcji XSL bardzo gwałtownie się rozwija. Najlepszym źródłem informacji o danym procesorze jest wymieniona strona WWW — tam znajdziemy informacje dotyczące zgodności narzędzia ze specyfikacjami, tam też są zamieszczone testy porównawcze. • Apache Xalan, http://xml.apache.org • James Clarks's XT, http://www.jclark.com/xml/xt • Lotus XSL Processor, http://www.alphaworks.ibm.com/tech/LotusXSL • Oracle XSL Processor, http://technet.oracle.com/tech/xml • Keith Visco's XSL:P, http://www.clc-marketing.com/xslp • Michalel Kay's SAXON, http://users.iclway.co.uk/mhkay/saxon
Struktury publikacji Struktura publikacji (ang. publishing framework) to termin nieco mglisty, nie stanowiący formalnej definicji. Na potrzeby niniejszej książki strukturą publikacji standardu XML nazwiemy zestaw narzędzi XML wykonujących przetwarzanie, przekształcanie (transformację) oraz dodatkowe czynności na dokumentach XML w aplikacji. Przetwarzanie i transformacja są zazwyczaj wykonywane za pomocą wspomnianych wyżej narzędzi; struktura publikacji łączy zaś wszystkie te operacje w jedną całość z interfejsem API Javy i zapewnia standardowy interfejs całości. W bardziej zaawansowanych strukturach możliwe jest przetwarzanie zarówno statycznych dokumentów XML, jak i tych stworzonych w aplikacjach Javy. Niektóre udostępniają także edytory i mechanizmy do tworzenia komponentów, dzięki czemu wygenerowany XML zgodny jest z wymaganiami narzuconymi przez daną strukturę. Ponieważ nie istnieje żadna specyfikacja określająca zachowanie takich struktur, wymienione poniżej rozwiązania są bardzo różne. Każde posiada cechy, które sprawiają, że warto się mu przyjrzeć bliżej. Niektóre struktury są rozprowadzane na zasadzie oprogramowania open source (OSS),
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 34
Dlaczego warto korzystać z technologii XML?
35
są więc nie tylko ogólnie dostępne, ale także otwarte w tym sensie, że można sprawdzić, w jaki sposób dane funkcje zostały zaimplementowane. Kiedy później zajmiemy się budową poszczególnych komponentów aplikacji, wybierzemy taką strukturę, która najlepiej pasuje do danego zadania. Teraz jednak decyzję tę odkładamy, aby Czytelnik mógł sam zdecydować, co jest dla niego najlepsze. • Apache Cocoon, http://xml.apache.org • Enhydra Application Server, http://www.enhydra.org • Bluestone XML Server, http://www.bluestone.com/xml • SAXON, http://users.iclway.co.uk/mhkay/saxon
Edytory i środowiska IDE dla standardu XML Istnieje wiele potężnych parserów i procesorów XML. Tego samego nie można jednak powiedzieć o edytorach. Niestety, XML jest w tym aspekcie w podobnej sytuacji, co kilka lat temu HTML. Niewielka grupa specjalizowanych programistów „pisze XML” zazwyczaj w vi, emacsie lub Notatniku. Pojawiły się ostatnio edytory przeznaczone specjalnie dla XML-a, są to jednak jeszcze projekty niedojrzałe i ich praktyczna przydatność jest niewielka. Istotne postępy robi na tym polu IBM — dotychczasowe wyniki pracy tej firmy można zobaczyć na stronie http://alphaworks.ibm.com. Odsyłacze do najświeższego oprogramowania można też znaleźć w świetnym serwisie http:// www.xmlsoftware.com.
Przyszłość XML-a Aby dopełnić obraz XML-a, spróbujmy jeszcze przewidzieć, jak standard ten będzie wykorzystywany w przyszłości. XML często określa się mianem „technologii przyszłości”. W niejednej firmie nie zdecydowano się na korzystanie z tego standardu, twierdząc że to technologia jeszcze niedopracowana. Jednocześnie wszyscy przyznają, że jej znaczny wpływ na sposób tworzenia aplikacji jest już przesądzony. Po tym, jak omówiliśmy sposoby zastosowania XML-a, trudno zgodzić się z tezą o jego niedojrzałości. Ale twierdzenie, że standard ten zrewolucjonizuje proces tworzenia aplikacji jest jak najbardziej prawdziwe. Nawet ci, którzy nie korzystają jeszcze intensywnie z XML-a, są świadomi, że wkrótce będą to musieli robić — a to „wkrótce” jest z każdym dniem bliżej. Mimo całego tego zamieszania otaczającego technologię XML oraz wielkich nadziei, jakie ona ze sobą niesie, prawie niemożliwe jest przewidzenie, jakie znaczenie będzie ona miała za rok, czy nawet za pół roku. To trochę tak, jakbyśmy jakieś cztery lata temu próbowali przewidzieć przyszłość tego śmiesznego języka obiektowego zwanego Javą, przydatnego, owszem, do budowania apletów... Są jednak pewne tendencje w XML-u, które pozwalają domyślić się, jakie zmiany nastąpią w najbliższym czasie. Przyjrzyjmy się tym najbardziej rzucającym się w oczy.
Repozytoria konfiguracji Jak już wspomnieliśmy, XML jest coraz częściej wykorzystywany w konfiguracjach serwerów, ponieważ umożliwia niezwykle prostą reprezentację danych idealnie spełnia potrzeby plików konfiguracyjnych. Tradycyjnie pliki takie były dość zawiłe, trudne w użyciu i modyfikacji oraz specyficzne dla danego producenta. Spójrzmy na fragment pliku konfiguracyjnego serwera HTTP Apache (przykład 1.5).
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 35
Te dwa pliki konfiguracyjne mają całkowicie inną składnię. Choć w różnych usługach wykorzystywane będą zazwyczaj różne definicje DTD i nazwy elementów, to jednak XML umożliwia sformalizowanie i ustandaryzowanie formatowania plików, przyczyniając się do powstania uniwersalnego języka konfiguracyjnego. To są naprawdę dobre wiadomości dla administratorów sieci, systemów i dla programistów. Czytelnik może zauważyć, że pliki konfiguracyjne już omawialiśmy — dlaczego powracamy do tego tematu? Obecnie każdy serwer posiada lokalny plik (lub pliki) konfiguracyjne. Choć w niektórych serwerach ostatnio umożliwia się także odczytywanie konfiguracji poprzez usługi katalogowe, to jednak ta metoda przyswajana jest powoli i wymaga znajomości protokołu usług katalogowych, najczęściej LDAP (Lightweight Directory Access Protocol). Widać coraz silniejszą tendencję tworzenia repozytoriów XML zawierających konfiguracje (rysunek 1.6). Coraz chętniej korzysta się również z interfejsu Java Naming and Directory Interface™ dla standardu XML (usługa podobna do udostępniania plików). W takiej sytuacji XML może funkcjonować albo oddzielnie od
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 36
Dlaczego warto korzystać z technologii XML?
37
usług katalogowych, albo jako abstrakcyjna warstwa „na” usługach katalogowych, pozwalająca aplikacjom posiadającym jedynie parser XML uzyskiwać ustawienia konfiguracyjne. To znacznie prostsze rozwiązanie niż udostępnianie serwerom bibliotek LDAP. Ponadto, w miarę wzrostu liczby serwerów poprawnie interpretujących język XML, możliwość przechowywania konfiguracji w jednym, centralnym miejscu pozwala zapewnić wzajemną współpracę poszczególnych elementów systemu. Serwery HTTP będą mogły rozpoznawać, jakie mechanizmy serwletów są dostępne, i samodzielnie konfigurować połączenia. Serwery JavaBean mogą odnajdywać usługi katalogowe w sieci i rejestrować tam swoje „fasolki”, a także odkrywać bazy danych, które można wykorzystać do przechowywania obiektów. To tylko niektóre możliwości wiążące się z wprowadzaniem serwerów sieciowych (korzystających ze wspólnego repozytorium XML) w miejsce serwerów samodzielnych.
Rysunek 1.6. Repozytorium konfiguracji oparte na XML-u
XSP XSP to Extensible Server Pages, czyli rozszerzalne strony serwera. To jeszcze jeden akronim związany z XML-em, którego wprowadzenie może mieć ciekawe konsekwencje dla programowania w Javie. Na razie standard ten ma status projektu roboczego autorstwa Ricardo Rocha i Stefano Mazzocchiego, głównych programistów pracujących nad projektem Apache Cocoon. W czasie pisania tej książki projekt nie był jeszcze przyjęty formalnie przez W3C ani inną organizację, ale kiedy książka będzie w sprzedaży, sytuacja ta może już przedstawiać się inaczej. Mówiąc skrótowo, XSP to zewnętrzny interfejs struktury XML. Umożliwia tworzenie dynamicznych stron
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 37
38
Rozdział 1. Wprowadzenie
XML przetwarzanych i przekształcanych w ramach struktury. Strony takie umożliwiają współpracę pomiędzy aplikacjami. W systemie składowane są jako pliki statyczne. Czytelnicy zaznajomieni z elementami Javy uruchamianymi po stronie serwera zapewne odnoszą wrażenie, że jest to coś w rodzaju JSP, a przynajmniej „XML-owej” wersji JSP. To do pewnego stopnia prawda. XSP to interfejs do XML-a; stanowi alternatywny język skryptowy do tworzenia stron i całych serwisów WWW. W wielu aplikacjach biznesowych pisanych w Javie duży nacisk kładzie się na odseparowanie zawartości od aplikacji i logiki biznesowej. Podobną rolę spełnia XSP w aplikacjach opartych na XML-u. Choć taką separację warstw w ramach skompilowanego kodu umożliwia wiele dostępnych obecnie struktur XML-a, to jednak zmiany w formatowaniu samych danych zawartych w dokumencie XML wymagają zmian w kodzie Javy i rekompilacji. A do tego dochodzą jeszcze zmiany wynikające z prezentacji za pomocą odpowiedniego arkusza stylów XSL. W ramach interfejsu XSP można także zdefiniować proces opisujący transformacje XSLT zachodzące w dokumencie — transformacje zarówno programowe, jak i prezentacyjne. Przyjrzyjmy się przykładowemu dokumentowi XSP (opartemu na przykładzie z projektu roboczego XSP — przykład 1.7).
Przykład 1.7. Prosta strona XSP
Prosta strona w XSP
Czołem. Otwarto mnie razy.
W takiej stronie mamy tylko ładnie sformatowany i w prosty sposób weryfikowany język XML. Nie ma w nim logiki programistycznej. Na tym właśnie polega różnica pomiędzy językami XSP a JSP — logika i kod programu zdefiniowane są w odpowiednim arkuszu logiki (ang. logicsheet), a nie w samej stronie XSP. Dzięki temu zachowujemy całkowitą niezależność od języka takiej strony oraz abstrakcję konstrukcji specyficznych dla danego języka w ramach arkusza logiki. Transformację znacznika oraz reszty strony może obsługiwać np. arkusz logiki zaprezentowany w przykładzie 1.8.
Przykład 1.8. Arkusz logiki XSP
java.lang.*
private static int licznik = 0; private synchronized int obecnaLiczba() {
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 38
Co dalej?
39 return ++licznik; }
obecnaLiczba()
Java i XML
Wprowadzenie Co to jest? Jak z tego korzystać? Dlaczego z tego korzystać? Co dalej?
Tworzenie dokumentów XML Dokument XML Nagłówek Zawartość Co dalej?
Przetwarzanie kodu XML Przygotowujemy się Czytniki SAX Procedury obsługi zawartości Procedury obsługi błędów
Lepszy sposób ładowania parsera
"Pułapka!" Co dalej?
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 42
Nagłówek
43
Struktury publikacji WWW Wybór struktury Instalacja
Korzystanie ze struktury publikacji
XSP Cocoon 2.0 i dalej Co dalej?
&HelionCopyright;
Nagłówek Pierwszy element dokumentu dotyczy samego języka XML. Dokument stworzony w tym języku można podzielić na dwie podstawowe części: nagłówek, który przekazuje parserowi i aplikacjom XML informacje o tym, jak dany dokument ma być obsługiwany, oraz zawartość, czyli same dane XML. Pozwoli nam to odróżnić treść dokumentu XML od instrukcji przekazywanych aplikacjom z jego wnętrza. Zaczniemy od pierwszych kilku linijek przykładu, do miejsca JavaXML: Ksiazka (bez niego). Te początkowe wiersze to właśnie nagłówek dokumentu. Termin „nagłówek” nie jest formalnym pojęciem zdefiniowanym w specyfikacji XML, ale jest szeroko stosowany. W tej książce również będziemy z niego korzystać.
Instrukcje XML Pierwszym elementem każdego dokumentu XML jest instrukcja XML. Instrukcje języka XML stanowią określony podzbiór instrukcji przetwarzania (PI), o których mówiliśmy w poprzednim rozdziale. Powiedziano tam, że instrukcje PI są zazwyczaj przekazywane przez parser do aplikacji wywołującej i dopiero tam obsługiwane. Ale instrukcje przetwarzające, w których jako element docelowy podano xml, są przeznaczone dla samego parsera. Określają one rodzaj używanej wersji XML-a, arkusz stylów lub inne informacje potrzebne parserowi do poprawnego przetworzenia danych XML. Oto instrukcja XML:
Podobnie jak wszystkie inne instrukcje PI, ma ona postać i w tym przypadku określa, że używana wersja standardu XML to 1.0, a dokument nie jest samodzielnym dokumentem XML. Zauważmy, że instrukcja to niekoniecznie jedna para klucz=warto••; w tym przypadku określamy zarówno wersję dokumentu, jak i to, czy wymaga on jeszcze obecności jakiegoś innego dokumentu lub dokumentów. Kiedy parser „dowiaduje się”, że dany dokument nie jest samodzielny (ang. standalone), wie, że do określenia jego poprawności będzie konieczne wykorzystanie zewnętrznej definicji DTD. Gdyby atrybut standalone miał wartość yes, parser nie musiałby odwoływać się do zewnętrznej definicji DTD. W początkowej instrukcji może też znaleźć się deklaracja sposobu kodowania dokumentu. Tutaj, jak również w przedstawionym powyżej przykładowym dokumencie XML, tłumacz dodał informację o kodowaniu (encoding="ISO-8859-2") — bez niej w czasie późniejszego przetwarzania dokumentu parser zgłaszałby błędy po napotkaniu znaków spoza standardowego zestawu ASCII (czyli np. polskich liter ą, ć, ś itp.).
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 43
44
Rozdział 2. Tworzenie dokumentów XML
Kolejną grupę stanowią instrukcje XML odwołujące się do arkuszy stylów. W naszym przykładzie mamy dwie takie instrukcje:
Zazwyczaj instrukcja PI rozpoczynająca się od xml-[nazwa] odwołuje się do technologii związanej z XML-em i określonej w części [nazwa]. W tym przypadku chodzi o arkusz stylów, toteż instrukcja przekazywana jest do mechanizmu XSLT, a nie do parsera XML-a. W pierwszym wierszu odwołujemy się do domyślnego arkusza stylów, podając jego położenie i typ. W wierszu drugim widzimy odwołanie do alternatywnego arkusza stylów. W tym przypadku podano także atrybut media, który informuje procesor, dla jakiego rodzaju klientów jest przeznaczony dany arkusz. Mechanizmy publikacji omówimy w rozdziale 9., Systemy publikacji WWW.
Deklaracje typu dokumentu Po początkowych instrukcjach XML następuje deklaracja DOCTYPE. Deklaracja ta dysponuje własną, odrębną składnią, ponieważ jest wykorzystywana tylko do jednego celu — określenia definicji DTD wykorzystywanej w danym dokumencie XML. Spójrzmy jeszcze raz na deklarację DOCTYPE w naszym przykładzie:
W tym przykładzie definicją DTD dla dokumentu jest plik JavaXML.dtd, znajdujący się w lokalnym systemie plików. Parsery XML zazwyczaj dopuszczają stosowanie zwykłego lub lewego ukośnika (odpowiednio, dla systemów Unix i Windows), co pozwala uzyskać przenośność ścieżek dostępu pomiędzy różnymi platformami. Pierwszym parametrem deklaracji DOCTYPE jest element główny dokumentu, o którym powiemy za chwilę — tymczasem wystarczy informacja, że określa on bieżący dokument XML. Następnym argumentem jest albo słowo SYSTEM, albo PUBLIC. Ponieważ wpisaliśmy SYSTEM, parser XML będzie oczekiwał, że w następnym argumencie znajdzie się położenie pliku DTD, określone za pomocą jednolitego identyfikatora zasobów (URI). Słowo SYSTEM nie oznacza jednak, że plik DTD musi się koniecznie znajdować w systemie lokalnym. Można też użyć następującego zapisu:
Specyfikacja XML pozwala umieścić w tym miejscu dowolny poprawny identyfikator URI, a więc adres URL jest do przyjęcia. Użycie słowa PUBLIC oznacza, że definicja DTD, do której się odwołujemy, jest upubliczniona i mogą z niej korzystać wszyscy. W tym przypadku przed podaniem URI konieczne jest określenie nazwy publicznej. Poniższą deklarację można znaleźć na początku niektórych plików HTML, takich jak strona główna konsorcjum W3C, dostępna pod adresem http://www.w3c.org/:
Po słowie kluczowym PUBLIC następuje nazwa określająca publiczną definicję DTD wykorzystywaną w danym dokumencie. Parser XML najpierw stara się zlokalizować definicję DTD za pomocą jej nazwy, a dopiero kiedy to się nie uda — za pomocą dodatkowo podanego identyfikatora URI. Składnia nazwy publicznej definicji DTD jest szczegółowo opisana w specyfikacji XML, ale to na razie nie będzie nas interesować. Definicjom DTD i ich formatowi przyjrzymy się w rozdziale 4., Zawężanie XML.
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 44
Zawartość dokumentu
45
Zawartość dokumentu Skoro nagłówek mamy już za sobą, możemy przejść do zawartości dokumentu XML. Składają się na nią elementy, atrybuty i opisywane przez nie dane tekstowe.
Element główny Element główny (ang. root element) to najwyższy w hierarchii element dokumentu XML. Jego definicję stanowią zawsze pierwszy znacznik otwierający i ostatni znacznik zamykający w dokumencie. Element ten stanowi punkt wyjścia dla parsera lub aplikacji XML, umożliwiając im rozpoznanie początku i końca dokumentu. W naszym przykładzie elementem głównym jest :
Struktury publikacji WWW
Pierwsza zasada tworzenia elementów mówi, że nazwa elementu musi rozpoczynać się literą lub podkreśleniem, po którym może wystąpić dowolna liczba liter, cyfr, podkreśleń, łączników lub kropek. Nazwy nie mogą zawierać spacji — poniższy przykład nie jest poprawnym elementem XML:
Czytniki SAX Po informacjach wstępnych czas przejść do programowania. Nasz pierwszy program pobierze plik jako argument wiersza poleceń i przetworzy go. Zbudujemy wywołania wsteczne do procesu przetwarzania, dzięki czemu zdarzenia zachodzące w czasie tego procesu będą wyświetlane i umożliwią lepszą obserwację całego mechanizmu.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 55
56
Rozdział 3. Przetwarzanie kodu XML
Najpierw należy uzyskać egzemplarz (ang. instance) klasy zgodny z interfejsem SAX org.xml. sax.XMLReader. Interfejs ten definiuje sposób przetwarzania i pozwala na ustawienie funkcji i właściwości, które zostaną omówione w rozdziale 5., Sprawdzanie poprawności składni XML-a. Informacja dla osób znających już SAX 1.0 — interfejs ten zastępuje dotychczasowy org.xml. sax.Parser.
Instalacja czytnika Interfejs udostępniany przez SAX powinien być zaimplementowany we wszystkich parserach XML zgodnych z SAX-em. Dzięki temu SAX wie, jakie metody dostępne są dla wywołań wstecznych i jakie można użyć z poziomu aplikacji. Na przykład główna klasa parsera SAX w Xerces, org.apache.xerces.parsers.SAXParser, implementuje interfejs org.xml.sax.XMLReader. Jeśli mamy dostęp do źródeł parsera, to zobaczymy, że ten sam interfejs zaimplementowano w głównej klasie parsera SAX. Każdy parser musi posiadać jedną (a czasem więcej) klas, które implementują ten interfejs. I właśnie egzemplarz tej klasy musimy stworzyć w celu przetworzenia danych XML: XMLReader parser = new SAXParser(); // Tu robimy coś z parserem parser.parse(uri);
Osoby, które po raz pierwszy stykają się z interfejsem SAX, mogą być zaskoczone brakiem zmiennej egzemplarza o nazwie reader czy XMLReader (reader to po angielsku „czytnik”). Rzeczywiście, wydawałoby się, że tak powinny nazywać się te komponenty, ale w klasach SAX 1.0 zdefiniowano główny interfejs przetwarzający jako Parser i wiele zmiennych wywodzących się z tamtego kodu odziedziczyło właśnie nazwę parser. Tamta postać interfejsu została już teraz zaniechana, ponieważ duża liczba poczynionych zmian wymagała nowych przestrzeni nazw, funkcji i właściwości, ale konwencje nazewnicze wciąż obowiązują, a parser dobrze oddaje przeznaczenie tego egzemplarza. Mając to na uwadze, przyjrzyjmy się niewielkiemu programowi uruchamiającemu i tworzącemu egzemplarz parsera SAX (przykład 3.1). Program ten nie przetwarza faktycznie dokumentu, ale tworzy szkielet, w ramach którego będziemy mogli wykonać pozostałe ćwiczenia rozdziału. Faktyczne przetwarzanie rozpoczniemy w następnym rozdziale.
Przykład 3.1. Przykład użycia parsera SAX import org.xml.sax.XMLReader; // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera plik XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author * Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /**
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 56
Czytniki SAX
57
*
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); } /** *
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego. *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]"); System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } }
Czytelnik powinien umieć załadować i skompilować powyższy program, o ile tylko przygotował się w opisany wcześniej sposób i klasy SAX znajdują się w ścieżce dostępu do klas. Ten prosty program nie potrafi jeszcze zbyt wiele; jeśli uruchomimy go, podając wymyśloną nazwę pliku lub URI jako argument, powinien „pobrzęczeć” dyskiem i wyświetlić komunikat „Przetwarzanie pliku XML”. Wynika to stąd, że jedynie stworzyliśmy instancję parsera, a nie zażądaliśmy jeszcze przetworzenia dokumentu XML. Jeśli wynikły kłopoty z kompilacją powyższego pliku źródłowego, najprawdopodobniej problem tkwi w ścieżce dostępu do klas środowiska programistycznego lub systemu. Najpierw należy upewnić się, że zainstalowany został parser Apache Xerces (lub inny). W przypadku Xerces należy po prostu pobrać odpowiedni plik jar. Po rozpakowaniu archiwum uzyskujemy plik xerces.jar — właśnie on zawiera skompilowane klasy dla tego programu. Po dodaniu tego archiwum do ścieżki dostępu do klas nie powinno być problemów z kompilacją powyższego przykładu.
Przetwarzanie dokumentu Kiedy parser jest już załadowany i gotowy do wykorzystania, można przekazać mu do przetworzenia dokument. Tradycyjnie służy do tego metoda parse(), wchodząca w skład klasy org.xml.sax.XMLReader. Metoda ta przyjmuje albo wejście org.xml.sax.InputSource, albo zwykły identyfikator URI. Na razie nie będziemy zajmowali się wejściem InputSource i skorzystamy z URI. Identyfikator taki może być adresem sieciowym, ale my
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 57
58
Rozdział 3. Przetwarzanie kodu XML
użyjemy pełnej ścieżki dostępu do dokumentu, który przygotowaliśmy wcześniej. Gdybyśmy jednak chcieli skorzystać z dokumentów umieszczonych w sieci, powinniśmy pamiętać o tym, że aplikacja musi umieć znaleźć dany adres URL (tzn. musi być zapewnione połączenie z siecią). Do programu dodajemy więc metodę parse() oraz dwie procedury obsługi błędów. Ponieważ dokument musi zostać załadowany, czy to lokalnie, czy przez sieć, może wystąpić wyjątek java.io.IOException, który musimy przechwycić. Ponadto w czasie przetwarzania może zostać zgłoszony wyjątek org.xmlsax.SAXException (problem przy przetwarzaniu), a więc musimy dodać jeszcze dwie istotne instrukcje, kilka linijek kodu i już mamy gotową do użycia aplikację przetwarzającą XML: import java.io.IOException; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera plik XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author * Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /** *
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } } public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]");
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 58
Procedury obsługi zawartości
59
System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } }
Tak zmodyfikowany przykład kompilujemy i wykonujemy. Pierwszym argumentem programu powinna być pełna ścieżka dostępu do przetwarzanego pliku: D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: D:\prod\JavaXML\contents\contents.xml
Widząc taki nieciekawy wynik działania programu Czytelnik może zacząć wątpić, czy w ogóle cokolwiek się stało. Jednak dokument XML jest rzeczywiście przetwarzany, a gdybyśmy podali błędny identyfikator URI pliku, parser zgłosi wyjątek i poinformuje, że nie mógł przetworzyć pliku. Nie stworzyliśmy jednak jeszcze żadnych wywołań wstecznych, powodujących że SAX informuje w czasie przetwarzania, co się dzieje w danej chwili. Bez nich dokument jest przetwarzany w sposób niewidoczny dla użytkownika i bez interwencji aplikacji. Oczywiście, my mamy zamiar interweniować w ten proces, a więc musimy stworzyć metody wywołań wstecznych. To właśnie ta „interwencja” jest najważniejszym aspektem korzystania z parsera SAX. Wywołania wsteczne parsera umożliwiają podjęcie działania w czasie trwania programu. Przetwarzanie przestaje być takie nieciekawe jak powyżej — aplikacja zaczyna reagować na dane, elementy, atrybuty i strukturę przetwarzanego dokumentu, a w tym czasie współdziała jeszcze z innymi programami i klientami.
Korzystanie z InputSource Zamiast korzystania z pełnego identyfikatora URI, metodę parse() można także wywołać z argumentem w postaci org.xml.sax.InputSource. Właściwie o tej klasie niewiele można powiedzieć — to raczej klasa osłonowa (ang. wrapper) i pomocnicza. Klasa InputSource po prostu kapsułkuje informacje o pojedynczym obiekcie. W naszym przykładzie nie na wiele się to przyda, ale w sytuacjach, gdzie identyfikator systemowy, identyfikator publiczny lub strumień mogą być dowiązane do jednego identyfikatora URI, kapsułkowanie za pomocą InputSource może się okazać bardzo przydatne. Klasa posiada metody akcesora i mutatora do obsługi identyfikatorów systemowego i publicznego, kodowania znaków, strumienia bajtów (java.io.InputStream) oraz strumienia znaków (java.io.Reader). Jeśli w ten sposób przekażemy argument do parse(), to SAX gwarantuje również, że parser nigdy nie zmodyfikuje InputSource. Dzięki temu wiemy na pewno, że po wykorzystaniu parsera lub aplikacji obsługującej XML dane wejściowe pozostaną niezmienione. Wiele aplikacji opisywanych w dalszych częściach książki będzie wykorzystywało właśnie klasy InputSource, a nie specyficznego URI.
Procedury obsługi zawartości Aby nasza aplikacja mogła zrobić cokolwiek pożytecznego z przetwarzanymi właśnie danymi XML, w parserze SAX musimy zarejestrować procedury obsługi (ang. handlers). Procedura obsługi to po prostu grupa wywołań wstecznych zdefiniowanych w ramach interfejsu SAX i umożliwiających wywoływanie kodu aplikacji w przypadku zajścia konkretnych zdarzeń w czasie przetwarzania dokumentu. Trzeba zdać sobie sprawę z tego, że wywołania te będą następowały w czasie przetwarzania dokumentu, a nie po jego przetworzeniu. To między innymi dlatego SAX jest tak
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 59
60
Rozdział 3. Przetwarzanie kodu XML
potężnym interfejsem — umożliwia obsługę dokumentu sekwencyjnie, bez konieczności wczytywania go całego do pamięci. Ograniczenie takie posiada model Document Object Model (DOM), który zostanie omówiony w dalszej kolejności. W interfejsie SAX 2.0 istnieją cztery podstawowe procedury obsługi: org.xml.sax.ContentHandler, org.xml.sax.ErrorHandler, org.xml.sax.DTDHandler oraz org. xml.sax.EntityResolver. W tym rozdziale omówimy procedurę ContentHandler, umożliwiającą obsługę standardowych zdarzeń związanych z danymi dokumentu XML. Rozpoczęte zostanie również omawianie procedury ErrorHandler, za pośrednictwem której parser zgłasza znalezione w dokumencie błędy. Procedura DTDHandler zostanie omówiona w rozdziale 5. Procedura EntityResolver omawiana jest w różnych miejscach książki; teraz wystarczy zrozumieć, że działa ona dokładnie tak jak pozostałe i służy do tłumaczenia encji zewnętrznych wstawionych do dokumentu XML. Każdy z tych interfejsów może zostać zaimplementowany w klasach aplikacji wykonujących specyficzne zadania. Klasy implementacyjne rejestrowane są w parserze metodami setContentHandler(), setErrorHandler(), setDTDHandler() i setEntityResolver(). Następnie parser wykonuje wywołania wsteczne tych metod w razie uruchomienia konkretnego programu obsługi. W naszym przykładzie zaimplementujemy interfejs ContentHandler. W interfejsie tym zdefiniowano szereg istotnych metod cyklu przetwarzania, na które nasza aplikacja może reagować. Przede wszystkim musimy dodać odpowiednie instrukcje import do pliku źródłowego (w tym klasę i interfejs org.xml.sax.Locator i org.xml.sax.Attributes — oraz nową klasę implementującą te metody wywołań wstecznych. Ta nowa klasa zostanie dodana na końcu pliku źródłowego SAXParserDemo.java: import java.io.IOException; import import import import import
// Tutaj importujemy implementację czytnika XML (XML Reader). import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera pliki XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /** *
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 60
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego. *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]"); System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } } /** * MyContentHandler implementuje interfejs * ContentHandler i definiuje sposób zachowania * wywołań wstecznych SAX powiązanych z zawartością * dokumentu XML. */ class MyContentHandler implements ContentHandler {
SAX
/** Zmienna locator będzie zawierała informacje o położeniu */ private Locator locator; /** *
* Odwołania do Locator umożliwią uzyskanie * informacji o miejscu, w którym wystąpiło wywołanie wsteczne. *
* * @param locator Locator -- obiekt dowiązany do * procesu wywołań wstecznych. */
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 61
62
Rozdział 3. Przetwarzanie kodu XML public void setDocumentLocator(Locator locator) { } /** *
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi * odwołaniami wszystkich procedur obsługi SAX oprócz * {@link #setDocumentLocator}. *
* * @throws SAXException jeśli coś pójdzie nie tak */ public void startDocument() throws SAXException { } /** *
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich * wywołaniach wstecznych wszystkich procedur obsługi. SAX.. *
* * @throws SAXException jeśli coś pójdzie nie tak */ public void endDocument() throws SAXException { } /** *
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie na deklarację XML). *
* * @param target String obiekt docelowy instrukcji PI * @param data String zawiera wszystkie dane wysłane do PI. * Zazwyczaj ma to postać jednej lub więcej par * atrybut - wartość. * @throws SAXException jeśli coś pójdzie nie tak. */ public void processingInstruction(String target, String data) throws SAXException { } /** *
* To oznacza początek odwzorowywania przedrostka przestrzeni nazw XML. * Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu XML, * ale nie jest to reguła (może pojawić się w dowolnym miejscu). * Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne * przed odwołaniem odnoszącym się do samego elementu * ({@link #startElement}). *
* * @param prefix String przedrostek dla odnalezionej * przestrzeni nazw. * @param uri String URI dla odnalezionej * przestrzeni nazw. * @throws SAXException jeśli coś pójdzie nie tak. */ public void startPrefixMapping(String prefix, String uri) { }
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 62
Procedury obsługi zawartości /** *
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna * przestrzeń nazw określona w odwołaniu * {@link #startPrefixMapping}. *
* * @param prefix String znalezionej przestrzeni nazw. * @throws SAXException jeśli coś pójdzie nie tak. */ public void endPrefixMapping(String prefix) { } /** *
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty * elementu, za wyjątkiem atrybutów specyficznych dla słownika XML, * takich jak: * xmlns:[namespace prefix] i * xsi:schemaLocation. *
* * @param namespaceURI String URI przestrzeni nazw, z którą * skojarzony jest ten element lub pusty * String. * @param localName String nazwa elementu (bez * przedrostka przestrzeni nazw, jeśli taki istnieje) * @param rawName String Wersja XML 1.0 nazwy elementu: * [namespace prefix]:[localName]. * @param atts Attributes -- lista atrybutów tego elementu * @throws SAXException jeśli coś pójdzie nie tak. */ public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { } /** *
* Oznacza, że osiągnięto koniec elementu. * (). Należy zauważyć, że parser * nie rozróżnia pomiędzy elementami pustymi i niepustymi, * a więc to będzie się odbywało identycznie w obu przypadkach. *
* * @param namespaceURI String URI przestrzeni nazw, z jaką skojarzony jest ten element * @param localName String nazwa elementu bez przedrostka * @param rawName String nazwa elementu w postaci XML. 1.0 * @throws SAXException jeśli coś pójdzie nie tak. */ public void endElement(String namespaceURI, String localName, String rawName) throws SAXException { } /** *
* Tutaj wyświetlamy dane tekstowe (zawarte wewnątrz elementu). *
* * @param ch char[] tablica znaków zawartych w elemencie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 63
63
64
Rozdział 3. Przetwarzanie kodu XML * @param start int indeks w tablicy, w którym zaczynają się dane. * @param end int indeks w tablicy, w którym kończą się dane. * @throws SAXException jeśli coś pójdzie nie tak. */ public void characters(char[] ch, int start, int end) throws SAXException { } /** *
* Tutaj informujemy o znakach białych, ignorowanych w oryginalnym dokumencie. * Zazwyczaj procedura taka uruchamiana jest jedynie wtedy, gdy w procesie * przetwarzania odbywa się też sprawdzanie poprawności. *
* * @param ch char[] tablica znaków zawartych w elemencie. * @param start int indeks w tablicy, w którym zaczynają się dane. * @param end int indeks w tablicy, w którym kończą się dane. * @throws SAXException jeśli coś pójdzie nie tak. */ public void ignorableWhitespace(char[] ch, int start, int end) throws SAXException { } /** *
* Tutaj pokazujemy encję pominiętą przez parser. Zdarzenie powinno * pojawiać się jedynie w parserach nie sprawdzających poprawności; * jego zachowanie zależy od konkretnej implementacji. *
* * @param name String nazwa pomijanej encji. * @throws SAXException gdy coś pójdzie nie tak. */ public void skippedEntity(String name) throws SAXException { } }
Dodaliśmy puste implementacje wszystkich metod zdefiniowanych w ramach interfejsu ContentHandler i nasz plik źródłowy kompiluje się. Oczywiście, takie puste implementacje nie umożliwiają obserwacji całego procesu, więc teraz przyjrzymy się po kolei wszystkim wymaganym metodom.
Lokalizator dokumentu Pierwsza metoda, jaką należy zdefiniować, to ta ustawiająca org.xml.sax.Locator dla dowolnego zdarzenia. W przypadku wystąpienia wywołania wstecznego klasa implementująca procedurę obsługi niejednokrotnie musi uzyskać informację o miejscu w pliku XML przetwarzanym przez parser. Dzięki temu aplikacja będzie mogła podjąć decyzję odnośnie danego zdarzenia i miejsca, w którym ono wystąpiło. Klasa Locator udostępnia szereg przydatnych metod, takich jak getLineNumber() i getColumnNumber(), zwracających bieżące miejsce w dokumencie XML. Ponieważ położenie to odnosi się tylko do bieżącego cyklu przetwarzania, klasy Locator powinno się używać wyłącznie w zakresie implementacji ContentHandler. Ponieważ nam
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 64
Procedury obsługi zawartości
65
może ona przydać się później, zachowujemy udostępniony egzemplarz klasy Locator do zmiennej składowej i drukujemy komunikat, informujący, że nastąpiło wywołanie wsteczne. Dzięki temu poznamy kolejność występowania zdarzeń SAX: /** Zmienna locator będzie zawierała informacje o położeniu */ private Locator locator; /** *
* Odwołania do Locator umożliwią uzyskanie * informacji o miejscu, w którym wystąpiło wywołanie wsteczne. *
* * @param locator Locator -- obiekt dowiązany do * procesu wywołań wstecznych. */ public void setDocumentLocator(Locator locator) { System.out.println(" * setDocumentLocator() została wywołana"); // Zachowujemy do ewentualnego wykorzystania w przyszłości. this.locator = locator; }
Później, jeśli konieczne będzie uzyskanie informacji o miejscu zdarzenia, do metody tej można dodać bardziej szczegółowe instrukcje. Jeśli jednak użytkownik chce pokazać, gdzie w dokumencie pojawiają się dane zdarzenia (np. numer wiersza, w którym wystąpił element), musi przypisać ten Locator zmiennej składowej do późniejszego wykorzystania w klasie.
Początek i koniec dokumentu Każdy proces ma początek i koniec. Oba te zdarzenia powinny wystąpić raz — pierwsze przed wszystkimi innymi zdarzeniami, a drugie po. Ten oczywisty fakt ma w aplikacjach krytyczne znaczenie — dokładnie informuje, kiedy przetwarzanie rozpoczyna się i kiedy kończy. SAX udostępnia metody wywołań wstecznych dla obu tych zdarzeń: startDocument() i endDocument(). Pierwsza metoda, startDocument(), wywoływana jest przed wszelkimi innymi wywołaniami wstecznymi, również przed tymi znajdującymi się w innych procedurach obsługi SAX, np. DTDHandler. Innymi słowy, startDocument() to nie tylko pierwsza metoda wywoływana wewnątrz ContentHandler, ale także pierwsza metoda w całym procesie przetwarzania (nie licząc metody setDocumentLocator(),wspomnianej przed chwilą). Dzięki temu wiemy, kiedy przetwarzanie rozpoczyna się, a nasza aplikacja może wykonać procedury, które muszą być uruchomione jeszcze przed tym przetwarzaniem. Druga metoda, endDocument(), jest zawsze wywoływana jako ostatnia, również bez względu na procedurę obsługi. Obejmuje to także sytuacje, w których proces przetwarzania zostaje zatrzymany w wyniku napotkania błędów. Błędy zostaną omówione w dalszej części książki; teraz Czytelnik powinien jednak wiedzieć, że są dwa ich rodzaje: błędy naprawialne i nienaprawialne. W przypadku wystąpienia błędu nienaprawialnego wywoływana jest metoda procedury ErrorHandler, po czym przetwarzanie kończy się wywołaniem metody endDocument(). W naszym przykładzie, kiedy obie te metody zostaną wywołane, wyświetlimy odpowiednie komunikaty na konsoli. /** *
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi * odwołaniami wszystkich procedur obsługi SAX oprócz * {@link #setDocumentLocator}.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 65
66
Rozdział 3. Przetwarzanie kodu XML *
* * @throws SAXException jeśli coś pójdzie nie tak. */ public void startDocument() throws SAXException { System.out.println("Rozpoczyna się przetwarzanie..."); } /** *
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich * wywołaniach wstecznych wszystkich procedur obsługi SAX. *
* * @throws SAXException jeśli coś pójdzie nie tak. */ public void endDocument() throws SAXException { System.out.println("...Przetwarzanie skończone."); }
Oba te wywołania wsteczne mogą zgłosić wyjątek SAXException. Jest to jedyny typ wyjątków zgłaszany przez SAX. Za pośrednictwem wyjątków udostępniany jest kolejny standardowy interfejs do procesu przetwarzania. Jednakże wyjątki te często pośredniczą w obsłudze innych wyjątków, wyraźnie wskazujących na to, z jakim problemem mamy do czynienia. Na przykład, jeśli plik XML jest przetwarzany za pośrednictwem sieci (poprzez adres URL), a połączenie nagle zostało przerwane, to zgłoszony zostaje IOException. Ale aplikacja korzystająca z klas SAX nie musi umieć przechwytywać tego wyjątku, ponieważ w ogóle nie musi wiedzieć, gdzie zlokalizowano zasób XML. Aplikacja może obsługiwać tylko jeden wyjątek — SAXException. Ten pierwszy wyjątek jest przechwytywany przez parser SAX i ponownie zgłaszany, tym razem jako SAXException; pierwotny wyjątek jest „kapsułkowany” w nowym. Dzięki temu wystarczy, żeby aplikacja przechwytywała jeden wyjątek, poprzez który dostarczane są także szczegółowe informacje o naturze problemu, jaki wystąpił w procesie przetwarzania. Klasa SAXException udostępnia metodę getException(), zwracającą właściwy wyjątek.
Instrukcje przetwarzania Instrukcje przetwarzania (PI) w dokumencie XML są elementami dość wyjątkowymi. Nie są postrzegane jako elementy samego XML-a i zamiast natychmiastowej obsługi, przekazywane są aplikacji wywołującej. Z tego względu do ich obsługi SAX udostępnia specjalne wywołanie wsteczne. Metoda ta otrzymuje obiekt docelowy (ang. target) instrukcji oraz dane przekazane do PI. Załóżmy, że życzymy sobie wyświetlenia na ekranie informacji w momencie pojawienia się wywołania: /** *
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie * deklarację XML). *
* * @param target String obiekt docelowy instrukcji PI * @param data String zawiera wszystkie dane wysłane do PI. * Zazwyczaj ma to postać jednej lub więcej par * atrybut - wartość. * @throws SAXException jeśli coś pójdzie nie tak. */ public void processingInstruction(String target, String data) throws SAXException {
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 66
Procedury obsługi zawartości
67
System.out.println("PI: Obiekt docelowy:" + target + " i dane:" + data); }
W rzeczywistej aplikacji, korzystającej z danych XML, w tym miejscu program mógłby otrzymać instrukcje do ustawienia wartości zmiennych lub wykonania metod związanych z tą konkretną aplikacją. Na przykład struktura publikacji Apache Cocoon mogłaby ustawić znaczniki związane z przekształcaniem danych po ich przetworzeniu lub wyświetlić skrót XML jako określony typ zawartości. Metoda ta, podobnie jak inne wywołania wsteczne SAX, zgłasza SAXException w razie wystąpienia błędu. Podczas omawiania instrukcji przetwarzania wspomnieliśmy także o deklaracji XML. Ta specjalna instrukcja przetwarzania udostępnia wersję, opcjonalne informacje o kodowaniu oraz o tym, czy dany dokument jest dokumentem samodzielnym:
Instrukcja ta przeznaczona jest specjalnie dla parsera XML. Umożliwia zgłoszenie błędu (np. o nieobsługiwanej wersji) na samym początku przetwarzania. Ponieważ instrukcja ta obsługiwana jest tylko przez parser, nie powoduje wywołania processingInstruction(). Należy uważać, aby nie stworzyć kodu oczekującego tej instrukcji lub informacji o wersji, ponieważ aplikacja nigdy nie otrzyma wywołania wstecznego do tej instrukcji przetwarzania. Właściwie to tylko parser powinien „interesować się” kodowaniem i wersją dokumentu XML, ponieważ elementy związane są z samym procesem przetwarzania. Kiedy już dane dokumentu zostaną udostępnione poprzez interfejs API Javy, szczegóły te stają się dla aplikacji nieistotne.
Wywołania związane z przestrzenią nazw Sądząc po tym, ile czasu poświęciliśmy przestrzeniom nazw w XML-u (oraz po stopniu skomplikowania tej problematyki), można przypuszczać, że jest to pojęcie dość istotne i że ma duży wpływ na przetwarzanie i obsługę danych XML. Oprócz schematu XML, przestrzenie nazw XML to z pewnością najważniejsza cecha dodana do języka XML od czasu opublikowania oryginalnego zalecenia XML 1.0. W interfejsie SAX 2.0 obsługę przestrzeni nazw wprowadzono na poziomie elementów. Dzięki temu możliwe jest rozróżnienie pomiędzy przestrzenią nazwy elementu (opisaną przedrostkiem i odpowiednim identyfikatorem URI) a lokalną nazwą elementu. W tym przypadku, pojęciem lokalna nazwa określamy nazwę elementu bez przedrostka. Na przykład lokalną nazwą dla JavaXML:Ksiazka jest po prostu Ksiazka. Przedrostek przestrzeni nazw to JavaXML, a identyfikator URI przestrzeni nazw (w naszym przykładzie) to http://www.oreilly.com/ catalog/javaxml. Istnieją dwa wywołania wsteczne do obsługi przestrzeni nazw (choć wywołania związane z elementami mogą także z nich korzystać). Wywołania te następują wtedy, gdy parser natrafi na początek i koniec odwzorowania przedrostków (ang. prefix mapping). Sam termin nie jest jeszcze znany, ale to pojęcie nie wprowadza nic nowego. Odwzorowanie przedrostków to po prostu element, w którym za pomocą atrybutu xmlns deklarowana jest przestrzeń nazw. Jest to często element główny (w którym może występować wiele różnych odwzorowań), ale nic nie stoi na przeszkodzie, aby tę rolę spełniał dowolny inny element dokumentu XML, jawnie deklarujący przestrzeń nazw. Na przykład:
Tu jakieś dane
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 67
68
Rozdział 3. Przetwarzanie kodu XML
W tym przypadku przestrzeń nazw deklarowana jest jawnie, na głębokości kilku poziomów zagnieżdżenia elementów. Wywołanie startPrefixMapping() otrzymuje przedrostek przestrzeni nazw oraz identyfikator URI skojarzony z tym przedrostkiem. Odwzorowanie uznawane jest za „zamknięte” lub „zakończone”, gdy zamknięty zostaje element deklarujący to odwzorowanie. Jedyny „haczyk” tego wywołania polega na tym, że zachowuje się ono niezupełnie sekwencyjnie — tak jak to ma zazwyczaj miejsce w interfejsie SAX; odwzorowanie przedrostków odbywa się bezpośrednio przed wywołaniem związanym z elementem deklarującym przestrzeń nazw. Oto przykład wywołania: /** *
* To oznacza początek odwzorowania przedrostka przestrzeni nazw XML. * Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu XML, * ale nie jest to reguła (może pojawić się w dowolnym miejscu). * Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne * przed odwołaniem odnoszącym się do samego elementu * ({@link #startElement}). *
* * @param prefix String przedrostek dla odnalezionej * przestrzeni nazw * @param uri String URI dla odnalezionej * przestrzeni nazw * @throws SAXException jeśli coś pójdzie nie tak */ public void startPrefixMapping(String prefix, String uri) { System.out.println("Początek odwzorowania dla przedrostka " + prefix + " odwzorowanego dla URI " + uri); }
W naszym dokumencie jedynym zadeklarowanym odwzorowaniem jest atrybut elementu głównego. Oznacza to, że powinniśmy oczekiwać tego wywołania przed wywołaniem związanym z pierwszym elementem (o którym za chwilę), ale już po wywołaniu startDocument() oraz po wszelkich instrukcjach PI, jakie znajdują się na początku dokumentu. Drugie z tej pary wywołań związanych z przestrzeniami nazw oznacza napotkanie końca odwzorowania i pojawia się bezpośrednio po znaczniku zamykającym elementu, w którym zadeklarowano odwzorowanie: /** *
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna * przestrzeń nazw określona w odwołaniu * {@link #startPrefixMapping}. *
* * @param prefix String znalezionej przestrzeni nazw * @throws SAXException jeśli coś pójdzie nie tak */ public void endPrefixMapping(String prefix) { System.out.println("Koniec odwzorowania dla przedrostka " + prefix); }
W przypadku pokazanego powyżej fragmentu dokumentu XML można oczekiwać następującego komunikatu po napotkaniu znacznika element2:
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 68
Procedury obsługi zawartości
69
Początek odwzorowania dla przedrostka mojaPrzestrzenNazw odwzorowanego dla URI http://mojUrl.pl
W ten sposób można poznać odwzorowywany przedrostek oraz skojarzony z nim identyfikator URI.
Wywołania związane z elementami Teraz najprawdopodobniej przygotowani już jesteśmy na faktyczne pobranie danych z dokumentu XML. To prawda, że ponad połowa wywołań wstecznych SAX nie ma nic wspólnego z elementami, atrybutami i danymi XML. Wynika to stąd, że proces przetwarzania nie polega po prostu na przekazaniu aplikacji danych XML; aplikacja otrzymuje także instrukcje PI, dzięki którym wie, jakie działanie ma zostać podjęte; aplikacja „dowiaduje się” także, kiedy przetwarzanie rozpoczyna się i kończy, a nawet gdzie znajdują się białe znaki, które można zignorować. Jeśli niektóre z tych wywołań wydają się Czytelnikowi pozbawione sensu, to należy uzbroić się w cierpliwość — jeszcze w tym rozdziale niektóre zagadnienia zostaną wyjaśnione; więcej wiadomości na ten temat znaleźć można w rozdziale 5. Oczywiście, istnieją wywołania SAX służące do uzyskiwania dostępu do danych XML zawartych w dokumentach. Pierwsze trzy zdarzenia, jakie zostaną omówione, to początek i koniec elementu oraz wywołanie characters(). Informują one, kiedy przetwarzany jest element, jakie dane zawiera oraz kiedy parser napotkał znacznik zamykający elementu. Pierwsze z wywołań, startElement(), informuje aplikację o elemencie XML i ewentualnych jego atrybutach. Parametry wywołania to nazwa elementu (w różnych postaciach) oraz egzemplarz klasy org.xml.sax.Attributes (Czytelnik powinien przypomnieć sobie opisywaną wcześniej instrukcję importującą). Ta klasa pomocnicza zawiera referencje do wszystkich atrybutów elementu. Umożliwia proste przetwarzanie kolejnych atrybutów elementu w postaci podobnej do Vector. Oprócz możliwości odwołania się do atrybutu za pomocą indeksu (kiedy przetwarzamy wszystkie atrybuty po kolei), możliwe jest również odwołanie się poprzez nazwę. Oczywiście, w tej chwili Czytelnik powinien już być ostrożniejszy, słysząc słowo „nazwa” związane z elementem lub atrybutem XML — może ono znaczyć różne rzeczy. W tym przypadku można użyć albo pełnej nazwy atrybutu (z przedrostkiem przestrzeni nazw, o ile taki tam jest) — to nazwiemy „surową” nazwą; albo połączenia nazwy lokalnej i identyfikatora URI, jeśli wykorzystano przestrzeń nazw. Istnieją także metody pomocnicze, takie jak getURI(int index) i getLocalName(int index), dzięki którym można uzyskać dodatkowe informacje o przestrzeni nazw związanej z danym atrybutem. Cały interfejs Attributes stanowi więc wszechstronne źródło danych o atrybutach elementu. Jak wspomnieliśmy, nie tylko atrybuty, ale również nazwa samego elementu może przyjmować różne formy. I znów chodzi tu o przestrzeń nazw w XML-u. Najpierw dostarczany jest identyfikator URI przestrzeni nazw danego elementu. Dzięki temu element umieszczany jest w poprawnym kontekście i odpowiedniej relacji do pozostałych przestrzeni nazw w dokumencie. Następnie podawana jest lokalna nazwa elementu, czyli fragment bez przedrostka. Oprócz tego (na potrzeby zgodności wstecz), podawana jest „surowa” nazwa elementu. Chodzi tu o niezmodyfikowaną nazwę elementu, zawierającą ewentualny przedrostek przestrzeni nazw — czyli to, co umieszczone było w dokumencie XML; dla naszego elementu Ksiazka byłaby to nazwa JavaXML: Ksiazka. Obecność tych trzech typów nazw umożliwia opisanie dowolnego elementu, z przestrzenią nazw, czy też bez niej. Skoro wiemy już, jak udostępnia się element i jego atrybuty, spójrzmy na implementację wywołania wstecznego SAX, wyświetlającą informację na ekranie. W tym przykładzie sprawdzamy, czy nazwa elementu posiada skojarzony z nią identyfikator URI przestrzeni nazw; jeśli tak — drukujemy przestrzeń nazw; jeśli nie — drukujemy komunikat, informujący, że z elementem nie skojarzono żadnej przestrzeni nazw: /**
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 69
70
Rozdział 3. Przetwarzanie kodu XML *
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty * elementu, za wyjątkiem atrybutów specyficznych dla słownika XML, * takich jak: * xmlns:[namespace prefix] i * xsi:schemaLocation. *
* * @param namespaceURI String URI przestrzeni nazw, z którą * skojarzony jest ten element lub pusty. * String * @param localName String nazwa elementu (bez * przedrostka przestrzeni nazw, jeśli taki istnieje) * @param rawName String Wersja XML 1.0 nazwy elementu: * [namespace prefix]:[localName] * @param atts Attributes -- lista atrybutów tego elementu. * @throws SAXException jeśli coś pójdzie nie tak. */ public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { System.out.print("startElement: " + localName); if (!namespaceURI.equals("")) { System.out.println(" w przestrzeni nazw " + namespaceURI + " (" + rawName + ")"); } else { System.out.println(" nie posiada skojarzonej przestrzeni nazw"); } for (int i=0; i java SAXParserDemo D:\prod\JavaXML\contents\contents.xml
Polecenie to powinno zwrócić dużo danych wyjściowych. Jeśli Czytelnik korzysta z systemu Windows, może się okazać konieczne zwiększenie bufora dla okna DOS-owego w takim stopniu, aby możliwe było przewinięcie ekranu i obejrzenie wszystkich danych wyjściowych. Dane te powinny mieć postać podobną do tych przedstawionych w przykładzie 3.22.
Przykład 3.2. Dane wyjściowe zwracane przez program SAXParserDemo Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana. Rozpoczyna się przetwarzanie... Początek odwzorowania dla przedrostka JavaXML odwzorowanego dla URI. http://www.oreilly.com/catalog/javaxml/ startElement: Ksiazka w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka) znaki: startElement: Tytul w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Tytul) znaki: Java i XML endElement: Tytul znaki: startElement: Spis w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Spis) znaki: startElement: Rozdzial w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Rozdzial) Atrybut: tematyka=XML znaki: startElement: Naglowek w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Naglowek) znaki: Wprowadzenie endElement: Naglowek
2
W tym oraz innych przykładach danych zwracanych przez programy możliwe jest, że zostały dodatkowo przełamane wiersze w celu poprawnego umieszczenia wydruku w książce. O ile jednak same dane tekstowe się zgadzają, to na pewno wszystko jest w porządku!
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 75
76
Rozdział 3. Przetwarzanie kodu XML znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=7 znaki: Co to jest? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=3 znaki: Jak z tego korzystać? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=4 znaki: Dlaczego z tego korzysta? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podrozdzialy=0 znaki: Co dalej? endElement: Temat ...
Takich danych pojawi się na wyjściu sporo, ponieważ przetwarzany dokument XML ma wiele elementów. Wyraźnie widać, że parser sekwencyjnie przetwarza każdy element, atrybuty elementu, dane w nim zawarte, elementy zagnieżdżone oraz znacznik końcowy elementu. Proces powtarza się dla każdego elementu. W naszym przykładzie został zastosowany parser Apache Xerces w wersji nie sprawdzającej poprawności dokumentu, a więc białe znaki są zgłaszane za pomocą wywołań characters(). W następnych dwóch rozdziałach zostaną omówione zagadnienia związane ze sprawdzaniem poprawności i wówczas będzie można porównać wynik działania. Teraz wiemy już, w jaki sposób parser zgodny z interfejsem SAX przetwarza dokument XML. Powinniśmy również rozumieć wywołania pojawiające się w czasie procesu przetwarzania oraz to, w jaki sposób można z nich uzyskać informacje potrzebne aplikacji. W następnych dwóch rozdziałach zostaną omówione zagadnienia związane ze sprawdzaniem poprawności dokumentu XML za pomocą dodatkowych klas SAX służących do obsługi definicji DTD. Teraz sprawdzimy, jakie błędy mogą się pojawić, gdy dokument nie będzie poprawny, co dzieje się, gdy dokument XML nie jest poprawny i jakie błędy mogą z tego wynikać.
Procedury obsługi błędów Oprócz interfejsu ContentHandler, SAX udostępnia również interfejs ErrorHandler, służący do obsługi sytuacji awaryjnych zaistniałych w czasie przetwarzania. Klasa ta działa podobnie jak omówiona wcześniej procedura obsługi dokumentu, ale zdefiniowano w niej jedynie trzy
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 76
Procedury obsługi błędów
77
wywołania wsteczne. Przy pomocy tych trzech metod SAX obsługuje i powiadamia o wszystkich możliwych błędach. Każda metoda otrzymuje informacje o błędzie lub ostrzeżeniu poprzez klasę SAXParseException. Obiekt ten zawiera numer wiersza, w którym wystąpił błąd, identyfikator URI przetwarzanego dokumentu (dokument przetwarzany lub zewnętrzne odwołanie wewnątrz tego dokumentu) oraz zwykłe informacje o błędzie, takie jak komunikat i dane ze śledzenia stosu. Ponadto każda metoda może zgłosić SAXException. Na początku może się to wydawać nieco dziwne — program obsługi wyjątków zgłaszający wyjątek? Należy jednak pamiętać, że każda z procedur obsługi błędów otrzymuje wyjątki związane z przetwarzaniem. Może to być ostrzeżenie, które nie powinno przerwać procesu przetwarzania, lub błąd, który należy rozwiązać, aby to przetwarzanie mogło być kontynuowane. Jednak wywołanie takie może także wykonywać operacje wejścia-wyjścia lub inne, które mogą spowodować zgłoszenie wyjątku — i wyjątek ten należy przekazać aż „na samą górę”, do aplikacji. Służy do tego właśnie wyjątek SAXException. Załóżmy, że procedura obsługi błędów otrzymuje powiadomienia o błędzie i zapisuje je do pliku dziennika błędów. Metoda taka musi być w stanie albo dopisać informacje do pliku istniejącego, albo stworzyć nowy plik. Gdyby w czasie przetwarzania dokumentu XML wystąpiło ostrzeżenie, także „dowiadywałaby” się o nim ta metoda. Ostrzeżenie miałoby na celu wywołanie odpowiedniej procedury i nie przerywałoby przetwarzania. Jednakże, jeśli procedura obsługi błędów nie byłaby w stanie zapisywać do pliku dziennika, musiałaby zakomunikować parserowi i aplikacji, że przetwarzanie ma zostać przerwane. Efekt taki można uzyskać poprzez przechwycenie wszystkich wyjątków wejścia-wyjścia i ponowne zgłoszenie ich aplikacji wywołującej, powodując przerwanie przetwarzania. Powyższy przykład wyjaśnia, dlaczego procedury obsługi błędów muszą mieć możliwość zgłaszania wyjątków (patrz przykład 3.3).
Przykład 3.3. Procedura obsługi, która może zgłosić SAXException public void warning(SAXParseException exception) throws SAXException {
}
try { FileWriter fw = new FileWriter("error.log"); BufferedWriter bw = new BufferedWriter(fw); bw.write("Ostrzeżenie: " + exception.getMessage() + "\n"); bw.flush(); bw.close(); fw.close(); } catch (Exception e) { throws new SAXException("Brak możliwości zapisu do pliku dziennika", e); }
Teraz można już zdefiniować szkielet procedury obsługi błędów i zarejestrować ją w parserze w taki sam sposób, jak w przypadku procedury obsługi zawartości. Najpierw trzeba zaimportować klasę SAXParseException i ErrorHandler: import import import import import import import import
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 77
78
Rozdział 3. Przetwarzanie kodu XML
Teraz w tym samym pliku z programem w Javie (znów na dole, po klasie MyContentHandler) należy zaimplementować interfejs ErrorHandler, zdefiniowany w ramach SAX-a. Podobnie jak przy omawianiu klasy ContentHandler, poniżej są przedstawione puste implementacje, które wkrótce zostaną wypełnione treścią: /** * MyErrorHandler implementuje interfejs SAX * ErrorHandler i definiuje zachowanie wywołań * wstecznych powiązanych z błędami w XML-u. */ class MyErrorHandler implements ErrorHandler { /** *
* Powiadomienie o ostrzeżeniu; żadne zasady XML nie zostały "złamane", * ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie. *
* * @param exception SAXParseException -- wyjątek, jaki nastąpił. * @throws SAXException gdy coś pójdzie nie tak. */ public void warning(SAXParseException exception) throws SAXException { } /** *
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że * złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni; * przetwarzanie może jeszcze być kontynuowane. *
* * @param exception SAXParseException -- wyjątek, który nastąpił. * @throws SAXException jeśli coś pójdzie nie tak. */ public void error(SAXParseException exception) throws SAXException { } /** *
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że * złamano regułę w ten sposób, że dalsze przetwarzanie * nie jest możliwe albo jest bezcelowe. *
* * @param exception SAXParseException -- wyjątek, który nastąpił. * @throws SAXException jeśli coś pójdzie nie tak. */ public void fatalError(SAXParseException exception) throws SAXException { } }
Aby procedura obsługi błędów mogła zostać użyta, należy ją zarejestrować w parserze SAX. Służy do tego metoda setErrorHandler() interfejsu XMLReader w naszej przykładowej metodzie
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 78
Procedury obsługi błędów
79
demo(). Metoda setErrorHandler() pobiera jako jedyny parametr interfejs ErrorHandler lub jego implementację: // Stwórz egzemplarze procedur obsługi ContentHandler contentHandler = new MyContentHandler(); ErrorHandler errorHandler = new MyErrorHandler(); try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } ...
A teraz spróbujmy sprawić, żeby na ekranie pojawiały się komunikaty w przypadku wywołania tych metod.
Ostrzeżenia Za każdym razem, gdy pojawi się ostrzeżenie (wynikające ze specyfikacji XML 1.0), metoda ta wywoływana jest w zarejestrowanej procedurze obsługi błędów. Są różne sytuacje powodujące wygenerowanie ostrzeżenia; wszystkie związane są z definicjami DTD i poprawnością składniową dokumentu. Zostaną one omówione w następnych dwóch rozdziałach. Tymczasem zdefiniujemy prostą metodę wyświetlającą numer wiersza, identyfikator URI i komunikat w przypadku wystąpienia ostrzeżenia. Ponieważ chcemy, aby w razie ostrzeżenia przetwarzanie zostało przerwane, zgłaszamy wyjątek SAXException i pozwalamy aplikacji nadrzędnej elegancko zakończyć działanie i zwolnić zasoby: /** *
* Powiadomienie o ostrzeżeniu; żadne zasady języka XML nie zostały złamane, * ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie. *
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 79
80
Rozdział 3. Przetwarzanie kodu XML "
Komunikat: " + exception.getMessage()); throw new SAXException("Napotkano ostrzeżenie"); }
Błędy niekrytyczne Błędy występujące w czasie przetwarzania, które można naprawić, ale które stanowią pogwałcenie fragmentu specyfikacji XML, postrzegane są jako błędy niekrytyczne. Procedura obsługi błędów powinna je przynajmniej odnotować w pliku dziennika, bo zazwyczaj są na tyle poważne, że zasługują na zwrócenie uwagi użytkownika lub administratora aplikacji; jednak nie są aż tak istotne, by przerwać jej działanie. Tak jak w przypadku ostrzeżeń, większość błędów niekrytycznych związanych jest z poprawnością dokumentu, która zostanie omówiona w odpowiednich rozdziałach. Podobnie jak w przypadku ostrzeżeń, nasza prosta procedura obsługi błędów wyświetli informacje przesłane do wywołania wstecznego i zakończy proces przetwarzania: /** *
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że * złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni; * przetwarzanie może jeszcze być kontynuowane. *
Błędy krytyczne Błędy krytyczne to te, które wymuszają zatrzymanie działania parsera. Zazwyczaj wynikają z niepoprawnego sformatowania dokumentu i ich pojawienie się oznacza, że albo dalsze przetwarzanie nie ma sensu, albo jest technicznie niemożliwe. Procedura obsługi błędu powinna niemal zawsze poinformować użytkownika lub administratora o wystąpieniu takiego błędu; pozostawienie aplikacji samej sobie może grozić całkowitym jej zawieszeniem. W naszym przykładzie będziemy emulowali zachowanie dwóch pozostałych metod i zatrzymamy przetwarzanie, wyświetlając komunikat o błędzie na ekranie: /** *
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że * złamano regułę w ten sposób, że dalsze przetwarzanie * nie jest możliwe albo jest bezcelowe. *
*
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 80
Po zakodowaniu procedury obsługi tego trzeciego typu błędu powinno być możliwe przekompilowanie pliku źródłowego i powtórne uruchomienie go na naszym dokumencie XML. Uzyskany wynik nie powinien różnić się od poprzedniego, bo dokument XML nie zawiera błędów, o których aplikacja mogłaby poinformować. W kolejnych podrozdziałach zostaną omówione błędy w dokumentach XML, w których nie jest wykonywane sprawdzanie poprawności.
Rozbijanie danych Skoro posiadamy już procedury obsługi błędów, to warto zobaczyć, jak one działają. Jak już to było wspomniane, większość ostrzeżeń i błędów niekrytycznych związanych jest z poprawnością składni dokumentu — zagadnienia te zostaną omówione w kilku następnych rozdziałach. Jest jednak jeden błąd niekrytyczny, który pojawia się w dokumentach XML nie sprawdzonych pod względem poprawności. Chodzi tutaj o wersję języka XML zgłaszaną przez dokument. Aby zobaczyć, jak ten błąd jest zgłaszany, zmieńmy nasz przykładowy dokument w następujący sposób:
Taki dokument nie jest poprawnie sformatowany. Uruchamiamy program SAXParserDemo na tak zmodyfikowanym pliku. Wynik przestawiony jest w przykładzie 3.5.
Przykład 3.5. Program SAXParserDemo wyświetlający błąd krytyczny D:\prod\JavaXML>java SAXParserDemo D:\prod\JavaXML\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... startElement: Ksiazka w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka) znaki: **Przetwarzanie błędu krytycznego** Wiersz: 13 URI: file:/D:/prod/JavaXML/contents/contents.xml Komunikat: The element type "JavaXML:Ksiazka" must be terminated by the matching end-tag "".
Parser zgłasza niepoprawne zakończenie elementu JavaXML:Ksiazka. Aby zrozumieć komunikat o błędzie, należy zdać sobie sprawę z faktu, że parser „widzi” znak ukośnika przed elementem JavaXML:Tytul i zakłada, że element, który musi zostać zamknięty, to JavaXML:
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 82
Lepszy sposób ładowania parsera
83
Ksiazka — czyli ten, który jest obecnie „otwarty”. Kiedy znajduje znacznik zamykający elementu JavaXML:Tytul, zgłasza niepoprawność tego znacznika jako zamknięcia otwartego elementu JavaXML:Ksiazka. Uważna analiza omawianej procedury obsługi błędów pozwala rozumieć, na jakie problemy można natknąć się w czasie przetwarzania i jak można ich unikać. W rozdziale 5. ponownie zajmiemy się procedurami obsługi błędów i przyjrzymy się problemom zgłaszanym przez parser wykonujący sprawdzanie poprawności składni.
Lepszy sposób ładowania parsera Choć potrafimy już przetwarzać dokumenty XML z wykorzystaniem interfejsu SAX, w naszym kodzie wciąż znajduje się pewien ewidentny błąd. Przeanalizujmy powtórnie proces tworzenia egzemplarza klasy XMLReader: try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Czy powyższy fragment nie budzi żadnych wątpliwości? Wskazówką może być jeszcze inny wiersz naszego kodu: // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser;
Jawnie importujemy implementację klasy XMLReader danego producenta, a następnie bezpośrednio tworzymy egzemplarz tej implementacji. Problem nie wynika z trudności tego zadania, ale z faktu, że gubimy jedną z najważniejszych cech Javy — przenośność. Takiego kodu nie da się uruchomić, a nawet skompilować na platformie nie wykorzystującej parsera Apache Xerces. A przecież możliwe jest nawet, że w zaktualizowanej wersji Xerces nazwa używanej klasy może ulec zmianie! A zatem nasz kod nie jest przenośny. W związku z tym zaleca się stworzenie egzemplarza klasy o nazwie klasy pobranej z implementacji. W ten sposób w kodzie źródłowym możliwe jest zastosowanie rozwiązania polegającego na zmianie prostego parametru String. Wszystko to jest możliwe w interfejsie SAX 2.0, a klasa, która udostępni potrzebną nam metodę, nosi nazwę org.xml.sax.helpers.XMLReaderFactory: /** * Próba utworzenia egzemplarza czytnika XML z nazwy klasy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 83
84
Rozdział 3. Przetwarzanie kodu XML * *
Po otrzymaniu nazwy klasy metoda ta usiłuje załadować * i stworzyć egzemplarz klasy jako czytnika XML.
* * @return Nowy czytnik XML. * @exception org.xml.sax.SAXException Jeśli klasa nie może zostać * załadowana; nie można stworzyć egzemplarza lub * wykonać rzutowania na XMLReader. * @see #createXMLReader() */ public static XMLReader createXMLReader (String className) throws SAXException { // Implementacja }
Ta statyczna metoda pobiera nazwę klasy parsera do załadowania, a zwraca wersję tej klasy w postaci egzemplarza rzutowanego na interfejs XMLReader (zakładając, że faktycznie implementowany jest XMLReader). Ewentualne problemy są zgłaszane do programu wywołującego za pośrednictwem SAXException. Teraz wystarczy dodać jedną instrukcję importującą, usunąć odwołanie do parsera konkretnego producenta, wprowadzić powyższe zmiany i już można przekompilować nasz kod źródłowy: import java.io.IOException; import import import import import import import import
// To usuwamy // import org.apache.xerces.parsers.SAXParser;
No i znów mamy przenośny kod! Aby jeszcze bardziej rozszerzyć przenośność, warto przechowywać nazwę klasy parsera w pliku właściwości. Umożliwia to ładowanie klasy „na gorąco”, a kod
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 84
Uwaga! Pułapka!
85
można przenosić pomiędzy platformami bez konieczności rekompilacji — zmianie ulega jedynie plik właściwości. Tak przebiega proces odczytywania plików właściwości w programie: try { // Stwórz egzemplarz parsera XMLReader parser = XMLReaderFactory.createXMLReader( PropertiesReader().getInstance() .getProperty("parserClass")); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Klasa narzędziowa PropertiesReader posłużyła tutaj do odczytania pliku właściwości i zwrócenia wartości odpowiadającej kluczowi parserClass (wartość ta będzie zawierała nazwę klasy parsera, którą należy wykorzystać dla danej platformy. W naszych przykładach byłby to łańcuch org.apache.xerces.SAXParser. Oczywiście, można byłoby także skorzystać z systemowych właściwości Javy, ale nie są one odpowiednie dla rozproszonych aplikacji WWW przedstawionych w tej książce (obsługiwane są z poziomu wiersza poleceń). Często aplikacje rozproszone uruchamiane są jako całość, a nie indywidualnie, a więc określenie właściwości systemowych dla jednego tylko komponentu byłoby trudne.
Uwaga! Pułapka! Programista w trakcie pracy może napotkać wiele pułapek. Czytelnik powinien wiedzieć, jak unikać typowych błędów programistycznych związanych z używaniem interfejsu SAX; w dalszych rozdziałach zostaną także omówione pułapki związane z innymi interfejsami API.
Mój parser nie obsługuje interfejsu SAX 2.0. Co robić? Nie załamywać się. Przede wszystkim zawsze można zmienić parser na inny — utrzymanie zgodności z najnowszą wersją SAX należy do obowiązku producenta parsera. Jeśli producent nie nadąża za standardami, to znaczy, że w naszym parserze mogą też istnieć inne błędy. Istnieją jednak przypadki, kiedy zmuszeni jesteśmy korzystać z danego parsera (np. z powodu konieczności obsługi zastanego kodu lub aplikacji). Jednak nawet w takich sytuacjach nie wszystko jest stracone. SAX 2.0 wyposażony jest w klasę pomocniczą org.xml.sax.helpers.ParserAdapter, która powoduje, że implementacja klasy Parser w SAX 1.0 zachowuje się jak implementacja XMLReader w interfejsie SAX 2.0. To poręczne narzędzie pobiera implementację Parser z wersji 1.0 jako parametr wejściowy i dalej może być używane zamiast tej implementacji. Umożliwia ustawienie ContentHandler i poprawnie obsługuje wszystkie wywołania związane z przestrzenią nazw. Jedyna funkcja, do jakiej nie będziemy mieli dostępu, to obsługa pomijania encji
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 85
86
Rozdział 3. Przetwarzanie kodu XML
(nie była ona po prostu dostępna w implementacji 1.0 w jakiejkolwiek formie, więc nie może być emulowana). Sposób użycia tej klasy przedstawiony jest w przykładzie 3.6.
Przykład 3.6. Korzystanie z klasy Parser z SAX 1.0 jako klasy XMLReader z 2.0. try { // Zarejestruj parser w SAX Parser parser = ParserFactory.makeParser( "org.apache.xerces.parsers.SAXParser"); ParserAdapter myParser = new ParserAdapter(parser); // Zarejestruj procedurę obsługi zawartości myParser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów myParser.setErrorHandler(errorHandler); // Przetwórz dokument myParser.parse(uri); } catch (ClassNotFoundException e) { System.out.println( "Nie znaleziono klasy parsera."); } catch (IllegalAccessException e) { System.out.println( "Niewystarczające przywileje do załadowania klasy parsera."); } catch (InstantiationException e) { System.out.println( "Niemożliwe utworzenie egzemplarza klasy parsera."); } catch (ClassCastException e) { System.out.println( "Parser nie ma zaimplementowanego org.xml.sax.Parser"); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Jeśli Czytelnik dopiero zaczyna poznawać interfejs SAX i ma kłopoty ze zrozumieniem tego przykładu, nie powinien się martwić — w przypadku korzystania z najnowszej i najlepszej wersji SAX (2.0) prawdopodobnie nigdy nie będzie trzeba używać kodu podobnego do powyższego. Przydatny jest on tylko tam, gdzie konieczne jest korzystanie z parsera 1.0.
SAX XMLReader — wielokrotne użycie a współbieżność Jedną z najciekawszych cech Javy jest fakt, że w niezwykle prosty sposób można wielokrotnie używać tych samych obiektów. Cechę tę posiadają również parsery SAX. Po utworzeniu egzemplarza XMLReader możliwe jest ciągłe jego używanie i przekazywanie mu wielu (nawet setek) dokumentów. Kolejne dokumenty lub źródła InputSources przekazywane są parserowi, zatem może być on wykorzystany do różnych zadań. Parsery nie są jednak współbieżne. Kiedy już rozpoczął się proces przetwarzania, parsera nie będziemy w stanie wykorzystać ponownie dopóty, dopóki to pierwsze przetwarzanie nie zostanie zakończone. Dla tych z Czytelników, którzy lubią korzystać z algorytmów rekurencyjnych, to właśnie może stanowić pułapkę. Kiedy spróbujemy użyć parsera wtedy, gdy ten jest akurat w trakcie przetwarzania innego dokumentu, zgłoszony zostanie raczej niemiły SAXException i całe przetwarzanie zostanie zatrzymane. Jaki z tego wy-
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 86
Uwaga! Pułapka!
87
nika wniosek? Dokumenty należy przetwarzać jeden po drugim, albo — godząc się na wszelkie tego skutki — tworzyć więcej egzemplarzy parsera.
Locator w złym miejscu Kolejną niebezpieczną (a z pozoru niewinną) cechą zdarzeń SAX jest fakt udostępniania egzemplarza Locator poprzez wywołanie metody setDocumentLocator(). W ten sposób aplikacja poznaje źródło zdarzenia SAX, co umożliwia podjęcie decyzji o dalszym przetwarzaniu oraz sposobie reagowania na inne zdarzenia. Jednakże to miejsce jest określone poprawnie jedynie na czas istnienia egzemplarza ContentHandler. Po ukończeniu przetwarzania Locator nie jest już poprawny (szczególnie wtedy, gdy rozpoczyna się następne przetwarzanie). Błędem popełnianym przez nowicjuszy jest przechowywanie referencji do obiektu Locator wewnątrz zmiennej należącej do klasy spoza wywołania wstecznego: public void setDocumentLocator(Locator locator) { // Zachowanie Locator w klasie poza ContentHandler mojaInnaKlasa.setLocator(locator); } ... public metodaInnejKlasy() { // Próba użycia poza ContentHandler System.out.println(locator.getLineNumber()); }
To bardzo zły pomysł — Locator traci znaczenie, bo jesteśmy już poza implementacją ContentHandler. Często korzystanie z takiej zmiennej powoduje nie tylko otrzymywanie przez aplikację nieprawidłowych informacji, ale również uszkodzenie dokumentu XML. Innymi słowy, obiektu tego należy używać lokalnie, a nie globalnie. W naszej implementacji ContentHandler otrzymany Locator zachowaliśmy do zmiennej. Następnie moglibyśmy jej użyć w poprawny sposób (np. do podania numerów wierszy, w których napotkaliśmy poszczególne elementy): public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { System.out.print("startElement: " + localName + " w wierszu " + locator.getLineNumber()); if (!namespaceURI.equals("")) { System.out.println(" w przestrzeni nazw " + namespaceURI + " (" + rawName + ")"); } else { System.out.println(" nie posiada skojarzonej przestrzeni nazw"); } for (int i=0; i
Java i XML
Taka zmiana, choć nie wpływa na poprawność danych XML, nie jest zgodna z narzuconymi regułami — atrybut dataPublikacji nie został zadeklarowany w definicji DTD i w czasie przetwarzania zgłoszony zostanie błąd (przykład 5.8).
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 124
Wynik sprawdzania poprawności
125
Przykład 5.8. Błąd zgłaszany przez program SAXParserDemo D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... **Przetwarzanie błędu** Wiersz: 14 URI: file:///mnt/teksty_zdalny/biura/helion/javaxml/cwiczenia/contents.xml Komunikat: Attribute "dataPublikacji" must be declared for element type "JavaXML:Ksiazka". Błąd w przetwarzaniu: Napotkano błąd
Analizator przesyła błąd do implementacji ErrorHandler, która zgłasza błąd — w tym przypadku mówiący, że atrybut nie został zadeklarowany dla zamykającego go elementu. Oczywiście, poprawność dokumentu można zburzyć na wiele sposobów — to tylko jeden z nich; zawsze jednak zgłoszony zostanie ten sam błąd, warto więc poeksperymentować. Należy po prostu pamiętać, że jakiekolwiek pogwałcenie DTD spowoduje zgłoszenie błędu. Dotyczy to przypadków, w których zawartość dokumentu nie jest poprawna, elementy nie są prawidłowo zagnieżdżone, atrybuty znajdują się nie tam, gdzie trzeba, lub są niepoprawne itd.
Błędy krytyczne Co ciekawe, dokument niezgodny z zawężeniami DTD nigdy nie spowoduje wygenerowania błędu krytycznego. W czasie przetwarzania dokumentu nie mogą zajść takie warunki, które spowodują przerwanie tego procesu. Może nam się wydawać, że kontynuacja przetwarzania niepoprawnego dokumentu kłóci się z celowością sprawdzania poprawności. Należy jednak pamiętać o tym, że w większości przypadków dokumenty XML są generowane przez aplikacje. Innymi słowy, aplikacja otrzymuje dane wejściowe XML z innego programu lub podprogramu. Jeśli dane te są niepoprawne, aplikacja usiłująca z nich skorzystać powinna zgłosić aplikacji klienta błąd, a nie przerywać przetwarzanie. Co więcej, przetwarzanie to niejednokrotnie powinno być kontynuowane, aby możliwe było zakończenie procesu w sposób elegancki — w ten sposób aplikacja jest w stanie precyzyjnie zakomunikować, jakie błędy wystąpiły. Dokumenty błędnie sformatowane spowodują zatrzymanie przetwarzania, a dokumenty niepoprawne wskażą, że albo nastąpił błąd, który można poprawić, albo zaszła sytuacja, o której klient powinien wiedzieć, np. wprowadzono niepoprawne dane. Zastanówmy się, jak trudno byłoby obsługiwać edytor XML lub środowisko programistyczne IDE, jeśli za każdym razem, gdy nie do końca przestrzegaliśmy zawężeń DTD, edytor kończyłby działanie z błędem krytycznym, lub też odmawiałby w ogóle przetwarzania dokumentu; a przecież niektóre edytory mogłyby takie błędy poprawiać za nas! Dlatego właśnie niepoprawne dokumenty powodują zgłaszanie ostrzeżeń i błędów, ale nigdy błędów krytycznych. Jedyny błąd krytyczny, na jaki możemy się natknąć przy używaniu definicji DTD w dokumentach, których poprawność nie jest sprawdzana, to błąd składniowy w wykorzystywanej definicji DTD. To nie powinno Czytelnika dziwić, jako że błędy składniowe w dokumencie XML także powodują błędy krytyczne. Ma tutaj miejsce to samo rozumowanie — nie jest możliwe dalsze przetwarzanie lub sprawdzanie poprawności, jeśli nie można określić zawężeń (a tak jest, gdy składnia DTD nie jest poprawna). Powinniśmy zdawać sobie sprawę, że to nie to samo co błąd generowany, gdy mamy do czynienia z niepoprawnie sformatowanym dokumentem XML — główna różnica polega na
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 125
126
Rozdział 5. Sprawdzanie poprawności składni XML-a
tym, że definicja DTD nigdy nie jest postrzegana jako „poprawnie sformatowana”, bo przecież nie jest to tak naprawdę dokument XML. Ale rezultat wystąpienia błędów składniowych w DTD jest taki sam, jak rezultat przetwarzania niepoprawnie sformatowanych danych XML.
Interfejs DTDHandler Ostatnia ważna procedura obsługi udostępniania przez SAX rejestruje metody wywołań wstecznych w czasie procesu czytania i przetwarzania definicji DTD dokumentu. W interfejsie tym nie są zdefiniowane zdarzenia, jakie występują w czasie sprawdzania poprawności, a jedynie te, które występują w czasie czytania DTD. W podrozdziale dotyczącym pułapek zostaną omówione problemy wywoływane przez to zróżnicowanie. Ta procedura obsługi zachowuje się w taki sam sposób jak interfejsy ContentHandler i ErrorHandler, omawiane w rozdziale 3. — definiowane są dwie metody wywoływane w czasie przetwarzania. Sprawdzanie poprawności dokumentu XML jest bardzo ważne, ale zdarzenia związane z odczytywaniem dokumentu DTD — już mniej. Są tylko dwie metody, obie nie są wykorzystywane tak często, a więc prawdopodobnie rzadko Czytelnik będzie musiał korzystać z interfejsu DTDHandler (chyba że pisząc edytor lub IDE dla XML-a będzie chciał tworzyć lub przetwarzać dokumenty DTD w celu sprawdzenia poprawności zapisu i składni). Dlatego pokrótce omówimy tutaj te dwie metody udostępniane przez SAX, ale nie będziemy poświęcali im zbyt dużo czasu. O opcjonalnej procedurze obsługi SAX, służącej do pobierania innych informacji z DTD, można przeczytać w części DeclHandler, w dodatku A (pakiet org.xml.sax.ext).
Nieprzetwarzane deklaracje encji Pierwsze wywołanie wsteczne, unparsedEntityDecl(), wywoływane jest, kiedy deklaracja encji w DTD mówi, iż encja ta ma nie być przetwarzana przez parser XML. Choć nie omawialiśmy jeszcze przykładu takiego zachowania, nieprzetwarzane encje są dość typowe w dokumentach XML, w których istnieją odwołania do plików graficznych lub innych danych binarnych (często plików multimedialnych). Metoda ta pobiera nazwę encji, identyfikatory, publiczny i systemowy, oraz nazwę notacyjną encji. Nazwy notacyjne w XML-u nie były jeszcze omawiane. Spójrzmy na przykład dokumentu XML odwołującego się do pliku graficznego (np. do logo firmy) — przykład 5.9.
Przykład 5.9. Dokument XML z nieprzetwarzaną encją
&LogoFirmy;
Podczas przetwarzania parser stara się przetłumaczyć wszystkie encje i wstawić w ich miejsce przetworzoną wartość. Jednakże parser nie potrafi przetwarzać plików graficznych i powinien pozostawić dane binarne w postaci nieprzetworzonej. Można go o tym „poinformować” poprzez następującą definicję typu dokumentu:
Słowo kluczowe NDATA powoduje, że parser XML nie przetworzy tej encji. Gdyby ta definicja DTD była przetwarzana za pośrednictwem zarejestrowanej implementacji DTDHandler, informacja w deklaracji encji zostałaby przekazana do wywołania. Inną ważną sprawą jest tutaj fakt, że wywołanie następuje w miejscu deklaracji w DTD, a nie w chwili, gdy deklaracja ta jest przetwarzana w dokumencie XML. Oznacza to, że nawet jeśli encja nie znajduje się w dokumencie
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 126
Interfejs DTDHandler
127
XML, to i tak wywołanie nastąpi. To ma sens — przecież wywołanie to stanowi część interfejsu DTDHandler, a nie ContentHandler.
Deklaracje notacji Deklaracje notacji zawsze skojarzone są z nieprzetwarzanymi deklaracjami i stanowią przedmiot działania drugiej metody wchodzącej w skład DTDHandler. Ostatnia część powyższej deklaracji nieprzetwarzanej encji to słowo „gif”. Słowo to określa typ nieprzetwarzanej encji i musi stanowić odwołanie do typu zdefiniowanego w innym miejscu DTD, za pośrednictwem konstrukcji NOTATION. W ten sposób parser poznaje identyfikator URI dla danego typu — często jest to odwołanie publiczne dla typów binarnych. Przypomina to odwoływanie się do DTD z dokumentu XML, ponieważ specyficzne dane (w tym przypadku plik GIF) są kojarzone z publicznym identyfikatorem lub URI. Przetwarzanie definicji NOTATION i deklaracji nieprzetwarzanej encji powoduje w rezultacie, że parser XML nie przetwarza danych binarnych. A o to właśnie chodziło.
Wystąpienia takich deklaracji zgłaszane są zarejestrowanej procedurze obsługi poprzez wywołanie notationDecl(). Po wywołaniu metoda ta otrzymuje deklarację notacji, identyfikator systemowy i dostępny identyfikator publiczny. Podobnie jak w przypadku nieprzetwarzanych encji, metoda ta wywoływana jest podczas odczytywania definicji DTD, a nie samego dokumentu XML.
Rejestrowanie procedury obsługi Rejestrowanie implementacji DTDHandler w naszym parserze XML nie różni się niczym od rejestrowania procedur obsługi błędów i zawartości. Egzemplarz klasy przekazywany jest metodzie setDTDHandler(), a parser rejestruje odpowiednie zdarzenia związane z metodami SAX w tej klasie: import java.io.IOException; import import import import import import import import import
I cała reszta... Czytelnik może odnieść wrażenie, że interfejs procedury obsługi DTD został omówiony dość pobieżnie, szczególnie w porównaniu z innymi procedurami. Jednakże aplikacje wykorzystujące XML rzadko muszą rejestrować tego typu procedury. W systemach XML-owych często sprawdza się poprawność dokumentu, ale szczegółowe informacje odnośnie nieprzetworzonych encji rzadko przydają się na wyższym poziomie wykorzystania XML-a. Dlatego właśnie przejdziemy do innych szczegółowych informacji, bardziej przydatnych przy tworzeniu aplikacji korzystających z XML-a. Czytelnik mógłby oczekiwać, że teraz zostanie omówione sprawdzanie poprawności za pomocą XML Schema; schematy są jednakże w coraz większym stopniu wykorzystywane nie tylko do sprawdzania poprawności, ale także do zwykłej reprezentacji danych. Dlatego właśnie omawianie schematów z poziomu Javy zostało przeniesione do rozdziału 14. — wtedy Czytelnik będzie już znał XSL i różnego rodzaju praktyczne sposoby użycia XML-a w aplikacjach. Interfejsy SAX do obsługi schematów są o wiele potężniejsze niż DTDHandler i będą wymagały od Czytelnika większej wiedzy z zakresu XML-a. W niniejszym rozdziale omawianie sprawdzania poprawności danych XML zostanie zakończone przedstawieniem typowych problemów, na jakie można się natknąć w czasie tego procesu.
Uwaga! Pułapka! Podczas sprawdzania poprawności dokumentu XML na programistę czyha wiele pułapek. Poniżej zostaną omówione problemy, z którymi najczęściej mają do czynienia początkujący programiści XML, a także te, których rozwiązanie wcale nie jest łatwe. Warto dobrze się zapoznać z poniższym materiałem, bo opisywane trudności spędziły sen z oczu już niejednemu programiście.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 128
Uwaga! Pułapka!
129
Obsługa sprawdzania poprawności a obsługa definicji DTD Jednym z typowych nieporozumień odnośnie SAX-a i sprawdzania poprawności jest błędne założenie, że sprawdzanie poprawności dokumentu XML następowało automatycznie po zarejestrowaniu implementacji DTDHandler w parserze XML. Często zdarza się, że programista implementuje ten interfejs i rejestruje go w parserze, ale nie włącza funkcji sprawdzania poprawności parsera. Ten błąd wynika z zagubienia różnicy pomiędzy obsługą samego DTD a wykorzystaniem DTD do sprawdzania poprawności. W tym przypadku definicja DTD zostanie przetworzona, wystąpią też wszystkie wywołania związane z DTD (o ile są konieczne), jednakże nie nastąpi sprawdzenie poprawności dokumentu XML — zostanie on wyłącznie przetworzony. Należy pamiętać, że wynik sprawdzania poprawności poprawnego dokumentu XML wygląda niemal tak samo jak wynik przetwarzania dokumentu bez sprawdzania jego poprawności; zawsze należy śledzić, kiedy występuje sprawdzanie poprawności — w ten sposób można uniknąć błędów w aplikacji: try { // Stwórz egzemplarz parsera XMLReader parser = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser"); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Z tego nie wynika włączenie sprawdzania poprawności parser.setDTDHandler(dtdHandler); // Włącz sprawdzanie poprawności parser.setFeature("http://xml.org/sax/features/validation", true); // Wyłącz "świadomość" przestrzeni nazw parser.setFeature("http://xml.org/sax/features/namespaces", false); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Trzeba koniecznie pamiętać o tym, że zarejestrowanie DTD nie ma nic wspólnego z procesem sprawdzania poprawności; parser z zarejestrowaną implementacją DTDHandler nie zawsze sprawdza poprawność XML-a; zaś parser bez zarejestrowanej tej procedury może sprawdzić poprawność. To od cech (w implementacjach SAX 2.0) lub klasy (w implementacjach sprzed SAX 2.0) parsera XML zależy, czy sprawdzanie poprawności będzie się odbywało — a nie od zarejestrowanej procedury obsługi. Pamiętajmy o tym — implementacja DTDHandler do sprawdzania poprawności nie jest potrzebna; potrzebne jest natomiast ustawienie odpowiednich cech parsera lub użycie innej klasy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 129
130
Rozdział 5. Sprawdzanie poprawności składni XML-a
W czasie tworzenia priorytetem jest poprawność; w gotowej aplikacji liczy się szybkość Programista powinien wiedzieć, kiedy należy korzystać ze sprawdzania poprawności, a kiedy nie. Jednym z największych problemów związanych ze sprawdzaniem poprawności jest często niezwykle powolne działanie aplikacji produkcyjnej. Programista analizuje przyczyny takiego spadku wydajności pracy aplikacji, nie pamiętając o tym, że w środowisku produkcyjnym pozostawił włączone sprawdzanie poprawności. Zazwyczaj sprawdzanie poprawności powinno mieć miejsce tylko w czasie testowania lub w czasie działania procesu kontrolowania jakości (ang. Quality Assurance). Jeśli część aplikacji generuje lub modyfikuje pliki XML, sprawdzanie poprawności wynikowego dokumentu XML powinno odbywać się właśnie w czasie testowania. To proces kosztowny — parser przetwarza o wiele więcej danych i musi podjąć więcej decyzji dotyczących dokumentu. Po sprawdzeniu poprawności wyników generowanych przez aplikację zazwyczaj dobrze jest wyłączyć sprawdzanie poprawności. Aplikacja produkcyjna zyskuje dzięki temu na wydajności, a jeśli testowanie przeprowadzono zgodnie z zasadami tej sztuki, to nie powinny pojawić się żadne problemy. Jedyną sytuacją, w której nie należy wyłączać sprawdzania poprawności w aplikacji produkcyjnej, jest ta, w której w tworzeniu danych XML bierze aktywny udział klient — np. w narzędziach XML IDE lub GUI, albo ta, w której dane XML uzyskuje się od innych aplikacji — np. w e-biznesie. Wynika to stąd, że dane wejściowe są poza naszą kontrolą i mogą być niepoprawne — sprawdzanie poprawności jest więc zalecane, a nawet oczekiwane przez klienta. Jednak w większości przypadków gotowa aplikacja może pracować bez sprawdzania poprawności.
Co dalej? Czytelnik powinien już dobrze znać zasady tworzenia dokumentów XML oraz sposoby ich zawężania. W niniejszym rozdziale zostały omówione wszystkie najważniejsze aspekty korzystania z interfejsów i klas SAX. Czytelnik poznał już cykle przetwarzania i sprawdzania poprawności oraz dostępne wywołania. Powinien potrafić skonfigurować i użyć parsera XML oraz zarejestrować w nim różne procedury obsługi SAX. W następnym rozdziale zostanie przedstawiona kolejna specyfikacja — XSL, czyli rozszerzalny język arkuszy stylów. Omówienie przekształcania za pomocą XSL to wstęp do dyskusji o obiektowym modelu dokumentu i mechanizmach publikacyjnych, a także do bardziej dogłębnego poznawania zagadnień programowania aplikacji.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 130
6 Przekształcanie kodu XML Jeśli Czytelnik bierze udział w tworzeniu systemów wewnętrznych lub projektuje systemy, to już na tym etapie czytania powinien doceniać możliwości XML-a. Język reprezentujący dane w sposób niezależny od producenta, prosty w przetwarzaniu, przenośny i obsługiwany z poziomu Javy — czy to właśnie ten format danych rozwiąże wszystkie problemy ze współpracą pomiędzy aplikacjami? Możliwe. Jeśli jednak Czytelnik jest programistą zawartości, budowniczym aplikacji, lub też zaangażowany jest w prezentację aplikacji, może odczuwać zniecierpliwienie — po przeczytaniu pięciu rozdziałów nie wie jeszcze, jak generować zawartość dla klientów z wykorzystaniem XML-a, jak separować prezentacje od danych. W tym rozdziale zostanie rozpoczęte omawianie przekształcania danych XML. Jest to obszerny temat obszerny. Najpierw zostaną przedstawione cele i sposoby przekształcania danych XML. W następnym rozdziale przedstawiony zostanie procesor Java XSLT, który pobiera dokument XML i generuje różne rodzaje zawartości — często różniące się w sposób zasadniczy od pierwotnych danych XML. Czytelnik dowie się również, jak w tym procesie wykorzystywany jest obiektowy model dokumentu (DOM). Przekształcanie XML-a jest tak obszernym tematem, że nie sposób omówić tutaj wszystkich dostępnych konstrukcji składniowych. Referencyjne podejście do przekształcania przedstawione jest w książce XML Pocket Reference Roberta Ecksteina (O'Reilly & Associates). Przekształcaniu XML-a poświęcono również sporą część serwisu WWW grupy W3C: http://www.w3.org/Style/XSL/.
Cel Przed przystąpieniem do omawiania procesu przekształcania danych XML, warto powiedzieć kilka słów o samym celu przekształcania. Podobnie jak w przypadku omawiania istoty zawężania danych XML, dobre zrozumienie przekształcania umożliwi poprawne wykonywanie tego procesu. Przekształcanie nie jest rozwiązaniem ostatecznym w zakresie prezentacji danych i nie wszędzie powinno być wykorzystywane; jednak są sytuacje, w których przekształcanie oszczędzi wiele godzin naszej pracy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 131
132
Rozdział 6. Przekształcanie kodu XML
W następnych dwóch rozdziałach skoncentrujemy się na sposobie, w jaki dane XML mogą zostać przedstawione różnym klientom. Jednakże niekoniecznie oznacza to, że będziemy zajmować się prezentacją danych samemu użytkownikowi... właściwie o tym nie powiemy prawie nic! Jeśli Czytelnik czuje się teraz nieco zagubiony, to nic nie szkodzi. Najpierw przypomnijmy sobie, co rozumiemy pod pojęciem klienta.
Klient klienta mojego klienta Najczęściej klienta lub aplikację klienta opisuje się jako użytkownika końcowego. Ale pogląd, że klientem aplikacji jest po prostu człowiek siedzący przed przeglądarką WWW lub interfejsem GUI jest nieścisły. Powiedzieliśmy już o tym, jak różne mogą być klienty korzystające z aplikacji; teraz rozszerzymy tę definicję jeszcze bardziej. Weźmy na przykład mechanizm bazy danych. Dane w bazie rzadko wyświetlane są bezpośrednio użytkownikowi, bez wcześniejszego sformatowania ich przez inny program. Czy możemy więc powiedzieć, że z bazy nie korzystają żadne klienty? A co z aplikacją przetwarzającą dane o pogodzie z innej strony HTML i wyświetlającą je użytkownikowi w nowym formacie? Czy aplikacja ta ma dwa klienty? A np. system X Windows, gdzie wyświetlany obraz generowany jest na serwerze, a aplikacja zdalna jest klientem? Oczywiste jest, że definicja klienta jest bardzo pojemna. Na potrzeby niniejszej książki klientem będziemy nazywali wszystko to, co korzysta z danych aplikacji, programu lub mechanizmu. Klientem w tym pojęciu byłby więc program korzystający z opisanej wyżej bazy danych, a także użytkownik, który przegląda sformatowane dane. Program formatujący dane o pogodzie jest klientem; są nimi także użytkownicy korzystający ze sformatowanych danych. Jak widać, granica między pojęciami „użytkownik” a „program” zaciera się. W wielowarstwowym systemie, w którym uruchomiono bazę danych, pojemnik Enterprise JavaBean, mechanizm serwletów i strukturę publikacji istnieją cztery, pięć lub więcej klientów! Czytelnik winien zrozumieć, że w przypadku XML-a nie rozróżniamy pomiędzy klientem a programem korzystającym z danych. Pozwala to nam postrzegać przekształcanie danych w sposób bardziej użyteczny — jeśli aplikacja A wymaga danych (w formacie A) od aplikacji B (przechowującej dane w formacie B), musimy te dane przekształcić (ang. transform). W przekształcaniu biorą udział dwa formaty, natomiast same dane pozostają nienaruszone. Kiedy znamy już nową definicję klienta, to możemy jeszcze od procesu tego oddzielić szczegółowe informacje o aplikacji wykorzystującej dane. Jeśli aplikacja C wykorzystuje sformatowane dane aplikacji B w formacie C, musi się odbyć kolejne przekształcanie. Nie jest istotne, czy formaty te to HTML, SQL, XML zgodny z różnymi DTD, czy coś jeszcze zupełnie innego. Nie jest również istotne to, czy klient jest kolejnym programem, użytkownikiem czy systemem własnym. Przekształcanie to po prostu przekładanie z jednego formatu na drugi. Wszystkie te informacje pomogą Czytelnikowi zrozumieć, dlaczego przekształcanie danych XML jest tak ważne.
Ja nie rozumiem chińskiego ... Jak można się domyślać, najważniejszym celem przekształcania danych XML jest doprowadzenie ich do takiej postaci, aby były czytelne dla danego klienta. Czasami można to osiągnąć poprzez przekształcenie dokumentu XML zgodnego z pewną definicją DTD na dokument zgodny z inną definicją (lub schematem). Innym razem konieczne jest uzyskanie zupełnie innych dokumentów z dokumentu bazowego. Tak czy inaczej, potrzebne tutaj będą informacje dotyczące zawężania i sprawdzania poprawności danych XML. Nawet jeśli aplikacja „zna” format danych, to nie będzie go mogła jeszcze zinterpretować — do tego konieczne są definicje DTD, schematy lub inne zawę-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 132
Składniki
133
żenia dokumentu. Wtedy dopiero aplikacja „zrozumie”, jakiej konwersji można dokonać i jakie struktury danych są obecne w dokumencie źródłowym. Problemy pojawiają się wtedy, gdy wzrasta liczba permutacji współpracujących ze sobą aplikacji; innymi słowy, im więcej jest składników biorących udział w wymianie danych, tym więcej możliwości przekształcania tych danych. Każdy komponent musi znać zawężenia innych komponentów. Dlatego właśnie stworzono zestaw specyfikacji i standardów służących do przekształcania danych XML. Powstała warstwa pośrednia, niezależna od poszczególnych komponentów aplikacji, która umożliwia konwersję danych z jednego formatu lub stylu na inny; samym komponentom pozostawia się czuwanie nad logicznym przetwarzaniem danych. Tę powłokę pośrednią będziemy nazywali procesorem. Procesor ma za zadanie pobrać jeden lub więcej dokumentów, określić ich formaty i zawężenia i dokonać przekształcenia (transformacji), po której powstają dane w nowym formacie (a to może nawet oznaczać rozdzielenie ich na wiele plików). Dokument wynikowy może potem zostać użyty przez inny komponent. Oczywiście, ta aplikacja może z kolei przekazać dane do innego procesora, który przekaże dokument w jeszcze innym formacie trzeciemu komponentowi i tak dalej. W ten sposób aplikacje nie znające tego samego „języka” mogą się ze sobą porozumiewać. Poniżej omówione zostaną „podzespoły” biorące udział w tym procesie.
Składniki Przekształcanie danych XML jest bardzo przydatne, ale wcale nie łatwe w implementacji. Nie określono sposobów przekształcania w oryginalnej specyfikacji XML 1.0; zamiast tego powstały trzy oddzielne zalecenia dotyczące przekształcania. Jeden z tych sposobów (XPath) jest również wykorzystywany w specyfikacji XPointer, ale na pewno najważniejszym celem opisywanych niżej komponentów jest przekształcanie XML-a z jednego formatu na inny. Te trzy specyfikacje są ze sobą ściśle powiązane (i niemal zawsze wykorzystywane wspólnie), więc zwykle opisuje się je razem. Powstały opis jest prostszy w zrozumieniu, ale niekoniecznie poprawny technicznie. Innymi słowy, termin XSLT, oznaczający dokładnie „przekształcenia XSL”, często stosowany jest jako określenie zarówno rozszerzalnych arkuszy stylów (XSL), jak i specyfikacji XPath. Podobnie, za pomocą terminu XSL określa się często wszystkie te trzy technologie. Tutaj zalecenia te zostaną rozdzielone. Jednak w celu uproszczenia będziemy używać skrótów XSL i XSLT przemiennie — określając za ich pomocą cały proces przekształcania. Nie jest to może zgodne z wytycznymi wspomnianych specyfikacji, ale na pewno zgodne z ich duchem i prostsze w zrozumieniu.
Rozszerzalny język arkuszy stylów (XSL) XSL to rozszerzalny język arkuszy stylów (ang. Extensible Stylesheet Language). Służy do tworzenia arkuszy stylów. To obszerna definicja, którą można rozbić na dwie części: • XSL to język służący do przekształcania dokumentów XML; • XSL to zbiór słów XML służących do formatowania dokumentów XML. Te definicje są do siebie podobne. Jedna z nich opisuje przekształcanie z jednego formatu XML na inny; druga odnosi się do samej prezentacji zawartości dokumentu. Mówiąc prościej, XSL opisuje, jak przekształcać dokument z formatu A na format B. Składniki tego języka opisują sposób takiego przekształcania.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 133
134
Rozdział 6. Przekształcanie kodu XML
XSL a drzewa Aby zrozumieć istotę XSL, trzeba przede wszystkim uświadomić sobie, że wszystkie dane biorące udział w przetwarzaniu XSL mają strukturę drzewiastą (patrz rysunek 6.1). Nawet same reguły definiowane za pomocą arkuszy XSL umieszczane są w takiej strukturze. Dzięki temu przetwarzanie dokumentów XML (także posiadających strukturę drzewiastą) jest bardzo proste. Za pomocą szablonów dopasowuje się element główny przetwarzanego dokumentu XML. Następnie dalsze reguły („liście”) stosuje się na dalszych elementach („liściach”) dokumentu XML, aż do tych położonych najniżej. Elementy mogą być przetwarzane, przekształcane za pomocą stylów, ignorowane, kopiowane lub w inny sposób „obrabiane” na dowolnym etapie tego procesu.
Rysunek 6.1. Operacje na drzewach w XSL Struktura drzewiasta ma tę zaletę, że umożliwia grupowanie dokumentów XML. Jeśli element A zawiera elementy B i C i nastąpi jego przeniesienie lub skopiowanie to elementy w nim zawarte będą podlegały tej samej operacji. Dzięki temu obsługa dużych porcji danych za pomocą arkusza XSL jest spójna i przejrzysta. O budowie takiego drzewa powiemy więcej w następnej części, przy okazji omawiania standardu XSLT.
Obiekty formatujące Niemal cała specyfikacja XSL to definicje obiektów formatujących. Obiekt formatujący oparty jest na dużym modelu określanym — jak łatwo się domyślić — nazwą modelu formatującego. W ramach modelu zdefiniowano zestaw obiektów, które przekazywane są na wejście programu formatującego. Program taki „aplikuje” obiekty formatujące na całym albo na części dokumentu; w rezultacie powstaje nowy dokument składający się z wszystkich lub tylko z części danych oryginalnego dokumentu XML, ale już w nowym formacie, odpowiadającym obiektom wykorzystanym przez program formatujący. Jest to, jak widać, koncepcja bardzo luźno zdefiniowana; dlatego też w ramach specyfikacji XSL starano się stworzyć bardziej konkretny model, z którym obiekty miałyby być zgodne. Innymi słowy, istnieje obszerny zestaw właściwości i słów kluczowych,
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 134
Składniki
135
które można wykorzystać w obiektach formatujących. Mamy tutaj typy obszarów podlegających wizualizacji, właściwości linii, czcionek, grafiki i innych obiektów wizualnych, obiekty formatujące typu „inline” i blokowe oraz wiele innych konstrukcji składniowych. Obiekty formatujące są szczególnie intensywnie wykorzystywane przy konwersji tekstowych danych XML na formaty binarne, takie jak pliki PDF lub dokumenty binarne w rodzaju Microsoft Word. Przy przekształcaniu danych XML na innegy format tekstowy obiekty te rzadko wykorzystuje się jawnie. Ponieważ większość aplikacji używanych w przedsiębiorstwach jest obecnie przynajmniej częściowo oparta na architekturze WWW, a w funkcji klienta wykorzystywana jest przeglądarka WWW, skoncentrujemy się głównie na przekształcaniu do formatu HTML-a lub XHTML-a. Będzie to wymagało jedynie pobieżnego omówienia obiektów formatujących; temat obiektów formatujących jest jednak na tyle obszerny, że zasługuje na oddzielną książkę lub witrynę WWW. Więcej informacji na ten temat można znaleźć w specyfikacji XSL pod adresem http://www.w3.org/TR/WD-xsl.
Transformacje XSL (XSLT) Drugim istotnym elementem opisywanego zagadnienia są transformacje XSL. Językiem opisującym konwersję dokumentu z jednego formatu na drugi jest XSLT. Składnia XSLT opisuje głównie te transformacje, które nie generują danych binarnych. Chodzi tu na przykład o przekształcanie dokumentu XML do HTML-a lub WML-a (Wireless Markup Language). Faktycznie, specyfikacja XSLT opisuje składnię arkuszy XSL w sposób bardziej jawny niż sama specyfikacja XSL! Podobnie jak w przypadku XSL, XSLT to zawsze poprawnie sformatowany i poprawny dokument XML. Dozwolone konstrukcje opisane są w odpowiedniej definicji DTD. W celu korzystania z XSLT konieczne jest więc nauczenie się jedynie nowej składni — a nie całkowicie nowych struktur, jak to ma miejsce w przypadku definicji DTD. Podobnie jak w przypadku XSL, XSLT oparty jest na hierarchii drzewiastej — elementy zagnieżdżone to „liście” lub „potomki”. XSLT udostępnia mechanizm dopasowania wzorców występujących w oryginalnym dokumencie XML (za pośrednictwem wyrażeń XPath, które wkrótce zostaną przedstawione), a następnie stosowania formatowania na znalezionych w ten sposób danych. Formatowanie może polegać po prostu na wysłaniu danych pozbawionych nazw elementów XML na wyjście albo na wstawieniu ich do złożonej tabeli HTML i wyświetleniu w różnych kolorach. XSLT udostępnia również rozmaite popularne operatory, takie jak instrukcje warunkowe, możliwość kopiowania fragmentów drzewa dokumentu, zaawansowane dopasowania wzorca oraz dostęp do elementów XML poprzez ścieżki absolutne i względne. Wszystko to upraszcza proces przekształcania dokumentu XML na nowy format.
Język XML Path (XPath) XPath to ostatni element używany w procesie przekształcania XML-a. XPath to mechanizm umożliwiający odwoływanie się do różnych nazw elementów i atrybutów w dokumencie XML. Jak wspomnieliśmy wcześniej, wiele specyfikacji XML korzysta z technologii XPath, ale tutaj skupimy się jedynie na wykorzystaniu jej w ramach XSLT. Przy złożonej strukturze, jaką może mieć dokument XML, zlokalizowanie konkretnego elementu lub grupy elementów może sprawiać trudności — tym większe, że nie można zakładać istnienia definicji DTD lub innych zawężeń opisujących strukturę dokumentu. Musi istnieć możliwość przekształcania zarówno dokumentów podlegających, jak i nie podlegających sprawdzaniu poprawności. Adresowanie elementów w XPath odbywa się za pomocą składni odpowiadającej strukturze drzewiastej dokumentu XML oraz operującym na niej procesom i konstrukcjom XSLT.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 135
136
Rozdział 6. Przekształcanie kodu XML
Najprościej odwołać się do dowolnego elementu lub atrybutu w dokumencie XML poprzez podanie ścieżki do tego elementu względem elementu bieżącego (przetwarzanego w danej chwili). Innymi słowy, jeśli element B jest elementem bieżącym, a w nim zagnieżdżone są elementy C i D, to te dwa ostatnie najprościej zlokalizujemy za pomocą ścieżki względnej. Przypomina to ścieżki względne w strukturze katalogów systemu operacyjnego. W XPath zdefiniowano również sposób adresowania elementów względem elementu głównego. Konieczne jest tutaj odwołanie się do elementu nie znajdującego się w zakresie nazwy elementu bieżącego; innymi słowy — elementu nie zagnieżdżonego w obecnie przetwarzanym elemencie. W XPath zdefiniowano wreszcie składnię dopasowywania wzorca („znajdź element, którego elementem macierzystym jest E i którego elementem siostrzanym jest F”). W ten sposób zapełniono lukę pomiędzy ścieżkami absolutnymi i względnymi. We wszystkich wyrażeniach można również korzystać z atrybutów (i stosować na nich podobne dopasowania). W przykładzie 6.1 reprezentowane są różne sposoby użycia Xpath.
Przykład 6.1. Wyrażenia XPath
Jak widać, jedyną trudnością w procesie tworzenia szablonu jest sformułowanie wyrażenia XPath dopasowującego element lub elementy XML, które chcemy „obrabiać”. Najprościej zastosować względne wyrażenie XPath. Podobnie jak podajemy katalog lib/ względem katalogu głównego, możemy określać elementy po prostu podając ich nazwę — o ile tylko znajdujemy się „o stopień wyżej” w hierarchii. Procesor XSLT „ustawia” nas na samym szczycie hierarchii elementów, a więc element główny dokumentu określamy podając jego nazwę:
Przykładowy współdzielony plik z opisem praw autorskich.
Nareszcie coś zaczyna się dziać. Na razie nie musieliśmy korzystać z wyszukanych funkcji XSL-a, ale już widać efekt w postaci wyjściowych danych HTML, i to przy niewielkim wysiłku z naszej strony. Można zauważyć, że elementy, których specjalne znaczenie w arkuszu powinno być zniesione (np. nawiasy trójkątne), zostały bezproblemowo przeniesione do drzewa wyjściowego. Dzieje się tak, ponieważ w samym arkuszu XSL elementy nie wchodzące w skład specyfikacji XSL (np. head i body) są wstawiane do drzewa wyjściowego bezpośrednio. To umożliwia nam dodawanie znaczników bez konieczności znoszenia ich specjalnego znaczenia. Zanim przejdziemy dalej, powiemy jeszcze o tym, jak dopasować w szablonie specyficzny element. Załóżmy, że chcemy, aby tytuł dokumentu HTML był identyczny z zawartością elementu JavaXML:Tytul. Dobrze byłoby, gdyby nie trzeba było tworzyć oddzielnego szablonu dla tego elementu — nie ma tutaj żadnego formatowania, więc byłoby to działanie na wyrost. My chcemy tylko uzyskać wartość elementu i „wpleść” ją w nasz HTML. Jak łatwo się domyślić, coś takiego można zrobić za pomocą jednej prostej konstrukcji — mianowicie xsl:value-of. Konstrukcja ta, zamiast przetwarzać dalej wyrażenie XPath, powoduje uzyskanie wartości dopasowanych przez XPath elementów. Wyrażenie XPath podawane jest jako wartość atrybutu select tego elementu. Wkrótce Czytelnik przekona się, że atrybut ten stosowany jest w wielu elementach XSL i że zawsze służy do podawania ścieżki XPath. W naszym arkuszu chcemy dopasować element JavaXML: Tytul, będący podrzędnym względem elementu bieżącego — czyli znów nie musimy w wyrażeniu XPath stosować jakichś specjalnych zabiegów. Po prostu wstawiamy tam nazwę elementu:
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 140
Składnia
141
Zanim przejdziemy do bardziej skomplikowanych struktur i wyrażeń, warto wspomnieć o jeszcze jednej rzeczy. W ostatnim przykładzie dopasowaliśmy element JavaXML:Tytul. Jednak wybranie wartości tego elementu nie oznacza usunięcia go z hierarchii elementów stanowiącej dane wejściowe procesora XSLT. To nieco osobliwe — nie tylko wartość elementu pojawi się w tytule dokumentu; zostanie ona powielona w jego treści, w wyniku działania xsl:apply-templates. Jeśli to zachowanie wydaje się Czytelnikowi niewłaściwe, warto zauważyć, że dane wejściowe procesora są nienaruszalne — nie można ich zmienić, a jedynie określić, w jaki sposób mają być przetwarzane i jakie informacje należy do nich dodać. Aby uniknąć przetwarzania jakiegoś elementu, musimy stworzyć wyrażenie XPath nie obejmujące go albo spowodować, aby szablon dopasowywał element, ale nie generował żadnych danych wyjściowych. Innymi słowy, musimy stworzyć szablon jawnie dopasowujący element JavaXML:Tytul i zrobić tak, aby po jego napotkaniu nie były tworzone żadne dane wyjściowe:
Kiedy wszystkie szablony w ramach szablonu elementu głównego zostaną już zastosowane, dopasowany — i zignorowany — zostanie także powyższy element. Mam nadzieję, że Czytelnik spostrzegł, że to rozwiązanie jest nieeleganckie i zastanawia się, jak stworzyć wyrażenie XPath ignorujące określony element. O tym wszystkim powiemy!
Struktury sterujące XSL, jak każdy dobry język przetwarzania, udostępnia struktury sterujące. Nie przypominają one może instrukcji sterujących Javy czy C, ale bardzo pomagają w sterowaniu przetwarzaniem drzewa wejściowego w procesorze XSLT. Przyjrzymy się najpopularniejszym z nich i zobaczymy, jak za ich pomocą określić dane, do których mamy mieć dostęp, oraz jak ustalić kolejność i sposób uzyskiwania tego dostępu.
Filtrowanie w XPath Omawianie struktur sterujących XSL zaczniemy od omówienia sterowania przetwarzaniem drzewa dokumentów. Czynność ta nie wymaga stosowania żadnej specjalnej konstrukcji, ale łatwo ją wykonać za pomocą wyrażeń XPath. Na przykład, jeśli zbudujemy wyrażenie XPath mówiące, że element opisujący tytuł ma nie być dublowany, to będzie to równie sprawna struktura sterująca, jak „typowe” struktury sterujące, o których powiemy dalej. Problem elementu JavaXML:Tytul można rozwiązać w prosty sposób. Pamiętamy, że zestaw węzłów zwróconych z wyrażenia XPath jest przetwarzany hierarchicznie; innymi słowy, procesor XSLT „nie widzi” wszystkich elementów dokumentu XML naraz. Rozpoznaje wszystkie elementy na określonym poziomie zagnieżdżenia, a następnie przetwarza w głąb struktury drzewiaste poszczególnych elementów. W przypadku naszego dokumentu elementy postrzegane przez procesor XSLT znajdujące się w dokumencie głównym to te zagnieżdżone dokładnie o jeden poziom niżej: JavaXML:Tytul, JavaXML: Spis oraz JavaXML:Copyright. Chcemy wyłączyć z przetwarzania JavaXML:Tytul. Najprościej wykorzystać tutaj funkcję XPath not(). Służy ona do generowania zestawu węzłów nie pasujących do podanego wyrażenia XPath. Najpierw dodajemy atrybut umożliwiający wprowadzenie wyrażenia XPath do szablonów. Na razie wystarczy podać atrybut select z gwiazdką — czyli wybieramy wszystkie węzły potomne węzła bieżącego. Nazwiemy to wybieraniem osi, na której będziemy przeprowadzać operację — tutaj wybieramy oś potomną. Oczywiście, w ten sposób nie rozwiązujemy do końca naszego problemu, ale początek mamy już za sobą:
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 141
142
Rozdział 6. Przekształcanie kodu XML
Teraz musimy dokładnie dopasować zestaw wynikowy. Na końcu kryterium wyboru dodamy nawiasy kwadratowe, w których wstawimy wyrażenie opisujące zestaw węzłów i zwracające węzły do przetworzenia. W naszym przykładzie w miejscu tym pojawi się funkcja not(), a następnie węzły osi potomnej, których nie chcemy przetwarzać:
Wyrażenie, które teraz wstawimy, powinno zawierać nazwę (wraz z przedrostkiem przestrzeni) ignorowanego elementu, czyli tutaj JavaXML:Tytul. Jednakże to wciąż nie wszystko. Ponieważ wybieramy węzły znajdujące się na określonej osi, musimy poinformować procesor XSLT, skąd pochodzi węzeł, do którego się odwołujemy. To dziwne — dlaczego programista ma sam podawać miejsce pochodzenia węzła? Zauważmy jednak, że często wybierana oś nie jest osią potomną, a więc konieczne jest podanie „punktu odniesienia” — nawet w naszym prostym przykładzie. W tym celu używamy słowa kluczowego self. Dzięki niemu procesor dowiaduje się, że węzły umieszczone po tym słowie są potomne względem węzła bieżącego (JavaXML:Ksiazka). Do oddzielenia słowa kluczowego od elementów (w naszym przypadku jednego elementu) używamy podwójnego dwukropka (jeden dwukropek pozostaje zarezerwowany jako separator przestrzeni nazw).
Jeśli to wydaje się Czytelnikowi zagmatwane, nie powinien się przejmować — to jest zagmatwane! Zestawy węzłów, osie i transformacje to wcale nie banalna sprawa. Te zagadnienia warte są osobnej publikacji. Tymczasem Czytelnik powinien starać się zrozumieć jak najwięcej z powyższego opisu i odnotować, co wciąż pozostaje niejasne. Po przeczytaniu tego i następnego rozdziału Czytelnik może znaleźć dodatkowe wyjaśnienia na stronach W3C (http://www.w3.org). Może również przejrzeć i dołączyć się do listy adresowej http://www.mulberrytech.com.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 142
Składnia
143
Pętle XSL udostępnia również konstrukcje do sterowania przetwarzaniem, analogiczne do standardowych programistycznych struktur sterujących. Pierwszą konstrukcją sterującą, jaką omówimy, jest xsl:for-each. Doskonale nadaje się ona do iteracji po danych w ramach jednego typu elementu. Na przykład — nasz spis treści zawiera szereg rozdziałów. Przetwarzając kolejne rozdziały, chcemy wydrukować ich tytuły. Aby to osiągnąć, tworzymy nowy szablon w pliku XSL, dopasowujący element JavaXML:Spis. Tak powstaje baza, w której zbudujemy pętlę (inaczej nie mielibyśmy gdzie jej wstawić). Moglibyśmy dodawać kolejne elementy do pierwszego szablonu JavaXML:Ksiazka, ale wtedy nasz arkusz szybko zapełniłby się złożonymi wyrażeniami XPath, odwołującymi się do węzłów leżących wiele warstw niżej. Tworząc nowy szablon dla elementu JavaXML:Spis, umożliwiamy budowanie wyrażeń XPath, w których „bazą” jest właśnie JavaXML:Spis, a nie JavaXML:Ksiazka. Wyrażenia XPath pozostają dzięki temu proste, a cały arkusz stylu — czytelny. Po przejściu do nowego szablonu można wydrukować nagłówek informujący, że teraz wyświetlony zostanie spis treści — wystarczy bezpośrednio wstawić odpowiedni fragment kodu HTML. Następnie dodajemy linię poziomą (), oddzielającą tytuł od zawartości:
Spis treści
Zanim jednak stworzymy pętlę, musimy rozwiązać jeden problem związany z powyższym fragmentem. Jeśli Czytelnik nie widzi błędu, winien przypomnieć sobie, że plik XSL musi zawsze być poprawnie sformatowanym dokumentem XML. Nawet kiedy dodajemy statyczne fragmenty kodu HTML, to muszą one być zgodne z powyższą zasadą (co w praktyce czyni te fragmenty danymi XHTML!). A więc przy przetwarzaniu powyższego fragmentu zgłoszony zostałby błąd, ponieważ znacznik nie posiada odpowiadającego mu znacznika zamykającego. Z punktu widzenia XML-a nie jest to nic zaskakującego, ale jeśli Czytelnik jest przyzwyczajony do HTML-a, to tutaj natrafia na coś nowego. Proste rozwiązanie polega na stworzeniu pustego zapisu w znaczniku, ignorowanego przez przeglądarki przetwarzające wynikowy HTML:
Spis treści
Do takich zapisów trzeba się po prostu przyzwyczaić — z biegiem czasu stają się one równie naturalne jak pisanie w mniej formalnym standardzie HTML. Skoro ten drobny szczegół mamy już za sobą, możemy budować pętlę. Konstrukcja xsl:for-each pobiera wyrażenie XPath (wstawione w atrybut select) opisujące węzeł, po którym następować będzie iteracja. W naszym przypadku życzymy sobie, aby wyrażenie zwracało wszystkie elementy JavaXML:Rozdzial. Tyle potrafimy już zrobić:
Spis treści
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 143
144
Rozdział 6. Przekształcanie kodu XML
Teraz wystarczy dodać zawartość. Każdy przetwarzany węzeł na czas kolejnej iteracji pętli staje się węzłem bieżącym hierarchii. Oznacza to, że aby odwołać się do elementów i atrybutów zagnieżdżonych w elemencie JavaXML:Rozdzial, należy potraktować je jako podrzędne względem tego elementu, a nie elementu przetwarzanego w szablonie (JavaXML:Spis). My chcemy wydrukować nagłówek każdego rozdziału. Aby wynik wyglądał ładniej, każdy nagłówek będzie stanowił element nieponumerowanej listy HTML:
Spis treści
Wynikowy kod HTML zaczyna już wyglądać obiecująco! Informacje o tym, jak taką zawartość stworzyć w mechanizmie serwletów, Czytelnik znajdzie w rozdziale 9. Wynikowy kod HTML wygląda następująco:
Przykład 6.5. Dane HTML powstałe w wyniku zastosowania zmodyfikowanego arkusza XSL
Java i XML
Spis treści
Wprowadzenie
Pisanie w XML-u
Przetwarzanie XML-a
Struktury publikacji WWW
Przykładowy współdzielony plik z opisem praw autorskich.
Czytelnik prawdopodobnie chciałby już się dowiedzieć, w jaki sposób faktycznie została przeprowadzona ta transformacja; o tym powiemy jednak dopiero w następnym rozdziale, po omówieniu konstrukcji XSL. Przed używaniem procesora XSLT należy poznać składnię XSL-a — to będzie procentowało na dalszym etapie nauki. Czytelnik może teraz zadawać sobie pytanie: dlaczego właściwie w stylach XSL korzystamy z pętli? Czy nie prościej byłoby stworzyć nowy szablon dla elementu, który zamierzamy sformatować (np. JavaXML:Rozdzial) i obsłużyć formatowanie w ramach tego szablonu? Tak, byłoby to prostsze (i robiliśmy to już wcześniej w przypadku wspomnianego elementu), ale problemem staje się wtedy czytelność kodu. W naszym arkuszu doskonale widać, do czego służy pętla — na pewno jest to rozwiązanie czytelniejsze, niż gdybyśmy tworzyli szablony dla każdego elementu JavaXML:Naglowek oddzielnie. W prosty sposób daje się także wyświetlić tylko te dane, które chcemy uwidocznić — w naszym przypadku nagłówek. Nie musimy pokazywać innych zagnieżdżonych w szablonie elementów (np. JavaXML:PodzialSekcji), ale rozdziały są wykorzys-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 144
Składnia
145
tywane do formatowania w ramach listy (
) tworzonej w szablonie JavaXML:Spis. Gdybyśmy rzeczywiście stworzyli szablon dla rozdziałów, musielibyśmy pamiętać, że zawartość szablonu musi być elementem listy (). To mało eleganckie. Podobnie jak w przypadku innych aspektów języka XML, możliwość zrobienia czegoś nie oznacza jeszcze, że jest to do końca poprawne. Tak więc odpowiedź brzmi: owszem, byłoby prościej stworzyć szablon, ale nie jest to dobre rozwiązanie.
Wybór elementów do przetwarzania Oprócz stosowania pętli, często przydaje się także możliwość przetwarzania tylko tych węzłów, które spełniają pewne kryteria. Programistom od razu przychodzi tutaj na myśl konstrukcja typu if-then, obecna w większości języków. W języku XSL zachowanie takie symulujemy konstrukcją xsl:if, zwracającą tylko te węzły, które odpowiadają wymogom zarówno wyrażenia XPath, jak również kryteriom użytkownika. Przydaje się to wszędzie tam, gdzie konieczne jest przeanalizowanie wszystkich danych pewnego typu i gdzie chcemy wyświetlić lub sformatować w określony sposób jedynie podzbiór tych danych. W naszym przykładzie oddzielimy rozdziały dotyczące Javy od tych poświęconych głównie XML-owi. Informacje o tematyce rozdziału pobierzemy z atrybutu tematyka, już obecnego w naszym dokumencie XML. Atrybutem uzupełniającym konstrukcję xsl:if jest test. Wynikiem obliczenia wyrażenia test powinna być wartość „prawda” lub „fałsz”. W pierwszym przypadku element xsl:if będzie obliczany, w drugim ignorowany. Nic trudnego, prawda? Spójrzmy na nasz przykład. Za pomocą opisywanej konstrukcji wyświetlimy teraz tylko te rozdziały, które zawierają atrybut tematyka o wartości „Java”:
Spis treści
Mamy tutaj kilka nowych rzeczy. Nowy jest sposób odwołania do atrybutu XML. Zamiast nazwy (jak w przypadku elementów), jako przedrostek wstawiamy znak @. Dzięki temu procesor XSLT „wie”, że odwołujemy się do atrybutu, a nie do elementu. Spójrzmy także na wartość dosłowną „Java”, wstawioną w apostrofy (czyli jako tekst statyczny). W przypadku dwóch rozdziałów, w których wyrażenie nie jest prawdziwe (rozdziały 1. i 2.), nagłówek nie jest drukowany; pozostałe dwa, spełniające zadane kryteria, są przetwarzane i drukowane na ekranie. To dość typowy i przydatny sposób „podejmowania decyzji” w czasie transformacji; jednak w przypadku naszego dokumentu — wciąż nie najlepszy. Zamiast tylko pokazywać rozdziały o XML-u lub o Javie, możemy zażyczyć sobie wyświetlania nazwy rozdziału, a potem jego tematyki w nawiasach. Owszem, moglibyśmy osiągnąć to za pomocą konstrukcji xsl:if, ale konieczne byłoby użycie dwóch pętli; pierwsza sprawdzałaby, czy tematyką poszczególnych rozdziałów jest XML, a druga sprawdzałaby, czy tematyką jest Java. Nie tylko odbiłoby się to na wydajności, ale również spowodowało wyświetlenie rozdziałów w złej kolejności — rozdziały o XML-u byłyby pierwsze, a te o Javie — ostatnie. Chcemy więc przeprowadzić podobny test, ale tak, aby zadana czynność wykonywana była w obu przypadkach. Służy do tego element xsl:choose. Pozwala on na przetestowanie warunku i wykonanie pewnej czynności, gdy warunek został spełniony, a in-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 145
146
Rozdział 6. Przekształcanie kodu XML
nej, jeśli nie został. Element ten otacza blok czynności do wykonania w obu przypadkach. Wewnątrz bloku wstawiany jest element xsl:when („jeśli”), w którym za pomocą atrybutu test określa się rodzaj testu do przeprowadzenia. Test ten jest identyczny z podanym w atrybucie elementu xsl:if. Wewnątrz tego elementu wstawia się instrukcje przetwarzania, jakie mają zostać wykonane, gdy test zwróci wartość „prawda”. Różnica polega na tym, że jest jeszcze element xsl:otherwise („w przeciwnym razie”), zawierający instrukcje przetwarzania wykonywane, gdy warunek nie zostanie spełniony. Ten element zachowuje się podobnie jak słowo kluczowe default w instrukcji switch Javy. Do naszego przykładu wprowadzimy teraz drobne poprawki — za każdym razem drukowana będzie nazwa elementu i, na podstawie testu, wyświetlana odpowiednia tematyka:
Spis treści
(Tematyka: Java)
(Tematyka: XML)
Należy sobie zdać sprawę z faktu, że powyższy przykład działa tylko dlatego, że dla atrybutu tematyka istnieją tylko dwie wartości. Trzeba także pamiętać, że moglibyśmy po prostu wysłać wartość tego atrybutu na ekran. Oczywiście, ale w ten sposób nie dowiedzielibyśmy się nic nowego o XSL-u! A mówiąc poważnie, element xsl:choose bardzo przydaje się do sterowania przetwarzaniem zestawu węzłów zwróconego z wyrażenia XPath, szczególnie gdy pewien podzbiór węzłów musi zostać wyizolowany i potraktowany w inny sposób. Nasz wynikowy plik HTML coraz bardziej przypomina rzeczywisty spis treści (przykład 6.6).
Przykład 6.6. Wynikowy plik HTML po zastosowaniu instrukcji sterujących
Java i XML
Spis treści
Wprowadzenie (Tematyka: XML)
Pisanie w XML-u (Tematyka: XML)
Przetwarzanie XML-a (Tematyka: Java)
Struktury publikacji WWW (Tematyka: Java)
Przykładowy współdzielony plik z opisem praw autorskich.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 146
Składnia
147
Struktury sterujące umożliwiają wykonanie wielu różnych zadań; przydają się również do zrozumienia bardziej złożonych wyrażeń XPath oraz testów. W następnych rozdziałach Czytelnik dowie się, jak za pomocą języka XSL przetwarzać i tworzyć elementy i atrybuty uzupełniające te już obecne na wejściu procesora.
Elementy i atrybuty Teraz czytelnik powinien już rozumieć, jak dużą kontrolę nad dokumentem XML daje nam arkusz XSL. Nie powinno być zaskoczeniem, że można również definiować własne atrybuty i elementy. Mogą one służyć do obliczeń lub po prostu zostać dodane do danych wyjściowych. Najczęściej wykorzystuje się takie elementy w zaawansowanych szablonach i przy przetwarzaniu parametrów, tego tematu nie będziemy jednak obszernie omawiali. Aby dowiedzieć się więcej o tych bardziej zaawansowanych zastosowaniach arkuszy XSL, warto zapisać się na listę adresową XSL, na której tematyka ta jest często poruszana. Informacje o liście uzyskamy pod adresem http://www.mulberrytech.com/xsl/xsl-list. Jednym z typowych powodów, dla których tworzy się elementy i atrybuty w arkuszu stylów, jest konieczność zbudowania dynamicznych odwołań HTML wewnątrz danych XML. Aby to zademonstrować, do naszego arkusza dodamy sekcję zawierającą informacje o materiałach dodatkowych (być może nie nadaje się to do spisu treści, ale służy jako doskonały przykład!), zagnieżdżoną w elemencie JavaXML:Ksiazka (należy pamiętać o wyłączeniu sprawdzania poprawności — w przeciwnym razie zostaną złamane zasady zdefiniowane w DTD!):
Java i XML
Aby obsłużyć taki fragment, trzeba dodać nowy szablon do arkusza stylu. Można również dodać formatowanie HTML oraz pętlę przebiegającą po odsyłaczach w dokumencie — wszystko to już potrafimy:
Pożyteczne informacje dodatkowe
Węzeł PI w modelu DOM stanowi pewne odstępstwo od schematu, który poznaliśmy — aby zachować zgodność ze składnią interfejsu Node, metoda getNodeValue() zwraca wszystkie instrukcje w PI w postaci jednego łańcucha String. To pozwala w prosty sposób wyświetlić instrukcję PI na ekranie; wciąż jednak do pobrania nazwy PI musimy korzystać z metody getNodeValue(). Gdybyśmy pisali aplikację, która otrzymywałaby instrukcje PI z dokumentu XML, skorzystalibyśmy raczej z interfejsu ProcessingInstruction; dzięki temu uzyskujemy dostęp do takich samych danych, ale nazwy metod (getTarget() i getData()) są bardziej zgodne z formatem PI. Mając to na uwadze, możemy już dodać kod wyświetlający instrukcje przetwarzania: case Node.PROCESSING_INSTRUCTION_NODE: System.out.println(""); break;
Po skompilowaniu i uruchomieniu tak zmienionego programu znów może nas spotkać niespodzianka — żadne instrukcje przetwarzania w dokumencie nie zostały wyświetlone na ekranie! Dlaczego? Otóż kod w takiej postaci uzyskuje obiekt Document z parsera XML i przetwarza tylko element główny wejściowych danych XML. Ponieważ instrukcje przetwarzania znajdują się na tym samym „poziomie” co element główny — są one ignorowane. Aby to poprawić, musimy zmienić część kodu obsługującą węzeł typu Document. Wprowadzimy fragment podobny do tego, jakim obsługiwaliśmy elementy potomne w celu przetworzenia wszystkich struktur XML „najwyższego poziomu”, a nie tylko elementu głównego: case Node.DOCUMENT_NODE: System.out.println("\n"); // Rekurencyjne przetwarzanie elementów potomnych NodeList nodes = node.getChildNodes(); if (nodes != null) { for (int i=0; i
Copyright O'Reilly and Associates, 2000
--> &OReillyCopyright;
Takie zachowania mogą stanowić przyczynę błędu w aplikacji. Cóż więc za pożytek z typu węzła opisującego encję, jeśli i tak jest ona wcześniej przetwarzana przez parser? Otóż węzeł taki powstał z myślą o tworzeniu nowego, a nie przetwarzaniu istniejącego dokumentu XML. To zagadnienie stanowić będzie treść kolejnego podrozdziału.
Mutacje drzewa DOM Czytelnik obeznany z modelem DOM zapewne zauważył, że pominęliśmy jeden bardzo istotny temat — możliwość zmiany drzewa DOM. Możliwe jest proste dodawanie węzłów do drzewa DOM. Właściwie jeśli pominąć prostotę, ta zdolność modyfikacji i dodawania węzłów do drzewa jest faktycznie jedną z najczęściej wykorzystywanych i najbardziej cenionych cech. I w ten sposób powróciliśmy do kwestii transformacji XML-a. Dlaczego model DOM jest tak istotny dla procesora XSLT? Otóż nie tylko chodzi tu o dane wejściowe, udostępniane w prosty sposób; ważne jest także to, że procesor XSLT może tworzyć nowe drzewo DOM na potrzeby dokumentu wyjściowego — a także w prosty sposób kopiować, modyfikować, dodawać i usuwać węzły drzewa wejściowego. Takie przetwarzanie preferowane jest szczególnie w złożonych aplikacjach — często dane wejściowe przekazywane są na drzewo wyjściowe bez rzutowania klas czy tworzenia nowych egzemplarzy obiektów — to istotnie zmniejsza złożoność i zwiększa wydajność procesora XSLT. To dlatego poświęciliśmy tak dużo czasu modelowi DOM. Omówienie mutowania, na które z pewnością czeka Czytelnik, zawarte jest w kolejnych rozdziałach. Teraz omówienie tego tematu oddaliłoby nas od tematu arkuszy XSL, ale w dalszych rozdziałach powrócimy do zagadnień związanych z tworzeniem i modyfikacją XML-a. Zanim jednak przejdziemy do bardziej zaawansowanych rozdziałów, należy omówić pułapki, jakie mogą czyhać na programistę Javy korzystającego z potężnego interfejsu DOM.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 174
Uwaga! Pułapka!
175
Uwaga! Pułapka! Podobnie jak w poprzednich rozdziałach, zostaną tutaj omówione najczęstsze pułapki czyhające na programistów Javy zajmujących się XML-em, ze szczególnym uwzględnieniem modelu DOM. Niektóre z poniższych uwag mają charakter bardziej informacyjny niż praktyczny, ale na pewno przyczynią się do podejmowania właściwych decyzji odnośnie stosowania modelu DOM i pomogą w zrozumieniu, co dzieje się „pod maską” naszych aplikacji XML.
Pamięć i wydajność w modelu DOM Dużo czasu zajęło nam wcześniej omawianie powodów, dla których warto zdecydować się na DOM lub SAX. Już wcześniej podkreślaliśmy, że korzystanie z modelu DOM wymaga wczytania całego dokumentu XML do pamięci i przechowywania go tam w postaci struktury drzewiastej — o tym trzeba koniecznie pamiętać! Niezwykle często zdarza się sytuacja, w której programista ładuje cały zbiór złożonych dokumentów XML do procesora XSLT i zostawia je do przetworzenia w trybie offline. Po powrocie komputer z systemem Windows wyświetla straszliwy „niebieski ekran śmierci”, a Linux zalewa ekran komunikatami o braku pamięci. Powtórzmy więc jeszcze raz — należy uważać na DOM przy przetwarzaniu dużych ilości danych! Korzystanie z modelu DOM wymaga zaangażowania pamięci w ilości proporcjonalnej do rozmiaru i złożoności dokumentu XML. Nie ma sposobu obniżenia wymogów odnośnie pamięci. Co więcej, same transformacje to „zasobożerne” operacje; w połączeniu z wymaganiami pamięciowymi to naprawdę daje dużo do myślenia. Czyż więc programista nie powinien nigdy nie korzystać z DOM-a? Oczywiście, powinien korzystać! Trzeba jednak zachować dużą ostrożność i uważać na to, co przekazujemy do modelu. Jeśli operujemy na niewielkich, mniejszych niż megabajt dokumentach, prawdopodobnie żadne kłopoty nigdy nam się nie przydarzą. Większe dokumenty — podręczniki techniczne czy całe książki — mogą pochłonąć zasoby systemowe i wpłynąć na wydajność aplikacji.
Analizatory DOM zgłaszające wyjątki SAX W naszych przykładach związanych z modelem DOM nie wspomnieliśmy o wyjątkach zgłaszanych w wyniku przetwarzania dokumentu. Wynika to stąd, że — jak mówiliśmy — proces generowania drzewa DOM pozostawiono implementacji parsera, a te różnią się przecież miedzy sobą. Jednakże zawsze dobrze jest przechwytywać specyficzne wyjątki i odpowiednio na nie reagować. Odpowiednia zmiana głównej pętli parsera może być nieco zaskakująca. Oto wersja dla Apache Xerces: /** *
* Tutaj przetwarzamy plik i wyświetlamy dokument * za pomocą modelu DOM. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { parser.parse(uri); Document doc = parser.getDocument();
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 175
176
Rozdział 7. Czerpanie z XML-a // Wyświetlamy dokument z drzewa DOM // z wcięciem w postaci łańcucha pustego printNode(doc, ""); } catch (IOException e) { System.out.println("Błąd wczytywania URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd przetwarzania: " + e.getMessage()); } }
Niespodzianką nie jest IOException (wskazuje błąd w lokalizacji podanego URI, podobnie jak w przykładzie SAX). Zastanawia coś innego — możliwość zgłoszenia wyjątku SAXException. Nasz parser DOM zgłasza wyjątek SAX? Chyba zaimportowaliśmy nie te klasy! Nie, klasy są właściwe. Pamiętajmy o tym, co mówiliśmy wcześniej — możliwe jest własnoręczne zbudowanie struktury drzewiastej danych w dokumencie XML za pomocą SAX-a, ale DOM stanowi ciekawą alternatywę. To prawda, ale nie wyklucza to jeszcze użycia interfejsu SAX. Rzeczywiście SAX udostępnia wydajny i szybki sposób przetworzenia dokumentu; akurat w naszym przypadku dokument jest przetwarzany przez SAX i wstawiany do struktury DOM. Ponieważ nie istnieje standard rządzący sposobem tworzenia struktury DOM, takie rozwiązanie jest dopuszczalne, a nawet popularne. Nie należy więc dziwić się, że importujemy i przechwytujemy org.xml.sax.SAXException w aplikacjach DOM.
Co dalej? Rozdziały 1. – 7. stanowią pokaźny zbiór informacji o XML-u. Czytelnik powinien już potrafić posługiwać się XML-em, definicjami DTD i schematami oraz XSLT. To najważniejsze technologie w programowaniu z wykorzystaniem języka XML. W kolejnych rozdziałach pojawią się oczywiście jeszcze inne skróty i specyfikacje, ale omówione zagadnienia to klucz do korzystania z XML-a z poziomu Javy. Oprócz specyfikacji zostały omówione także SAX i DOM, zatem Czytelnik potrafi reprezentować większość danych XML w Javie. Można powiedzieć, że Czytelnik jest już oficjalnie programistą znającym Javę i XML! W następnym rozdziale powrócimy do omawiania interfejsów SAX i DOM; wykażemy ich zalety i wady. Przed zagłębieniem się w kodach aplikacji (czemu poświęcona jest reszta książki) zostanie omówiony interfejs JDOM. Twórcy JDOM starali się naprawić problemy i dokuczliwości związane z interfejsami SAX i DOM. Powstały w ten sposób interfejs API otwiera przed programistą XML-a nowe możliwości.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 176
8 JDOM Nasza podróż po krainie języka XML dobiegła półmetka. Czytelnik powinien już mieć ogólne pojęcie o przydatności opisywanych narzędzi — niektóre z nich są bardzo użyteczne, inne nieco siermiężne. Powinien również umieć korzystać z tych interfejsów i koncepcji w swoich aplikacjach, a przede wszystkim doceniać, jak bardzo może się mu przydać XML. W tym rozdziale — zanim zagłębimy się w specyficzne tematy związane z XML-em — zostaną omówione dodatkowe interfejsy programistyczne łączące Javę i XML. Najpierw omówimy pomocniczy interfejs programistyczny, Java API for XML Parsing (JAXP). Interfejs ten, opracowany przez firmę Sun, udostępnia abstrakcyjną warstwę w procesie uzyskiwania egzemplarza parsera SAX lub DOM; Czytelnik zapewne spostrzegł we wcześniejszych rozdziałach, że zadanie to nie wszędzie zostało ustandaryzowane (szczególnie chodzi tu o DOM) i nie przekłada się na niezależność XML-a względem konkretnego producenta. Po omówieniu interfejsu JAXP zostanie przedstawiony nowy interfejs programistyczny, JDOM. Interfejs ten nie jest spokrewniony z DOM-em w obszarze struktury czy implementacji, ale — podobnie jak DOM — oferuje kompletną reprezentację dokumentu XML. Został jednak stworzony z myślą o konkretnym celu — rozwiązaniu szeregu opisanych już problemów związanych z SAX-em i DOM-em (patrz podrozdziały Uwaga! Pułapka!) i zwiększeniu przydatności i wydajności względem istniejących interfejsów API dla Javy. Czytelnik pozna cel utworzenia, oferowane funkcje i przyszłość tego interfejsu jako alternatywy dla SAX-a, DOM-a i JAXP-a. Najpierw jednak do naszego zestawu narzędzi dodajmy interfejs JAXP.
Parsery i JAXP Czytelnik zainteresowany tematem XML-a i Javy prawdopodobnie natknął się na produkt firmy Sun o nazwie Java API for XML Parsing, najczęściej określanego skrótem JAXP. Wspomnieliśmy o nim również skrótowo w rozdziale 1. Jeśli wziąć pod uwagę, że JAXP jest często wymieniany jednym tchem z SAX-em i DOM-em, może nieco zaskakiwać fakt, że zajmujemy się nim dopiero teraz. Jednakże cały pakiet JAXP, wchodzący w skład javax.xml.parsers, to ledwie sześć klas, z których cztery są abstrakcyjne. Pozostałe dwie opisują wyjątki zgłaszane przez pierwsze cztery. Czytelnik zapewne pamięta, że podczas korzystania z interfejsu DOM (i SAX bez klasy XMLReaderFactory) trzeba jawnie zaimportować i wpisać odwołanie do klasy parsera danego producenta. W Apache Xerces odpowiednie klasy to org.apache.xerces.parsers.SAXParser oraz
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 177
178
Rozdział 8. JDOM
org.apache.xerces.parsers.DOMParser. Problem polega na tym, że zmiana parsera wymaga zmiany kodu aplikacji i rekompilacji. To istotna wada — dobrze byłoby, gdyby parsery można było dołączać na zasadzie „wtyczek”. Do tego właśnie służy interfejs JAXP. Kiedy korzystamy z interfejsu JAXP, to zamiast bezpośrednio importować klasę parsera określonego producenta, definiujemy ją za pomocą właściwości systemowej. JAXP odczytuje tę właściwość i zajmuje się załadowaniem danego parsera. W ten sposób zmiana implementacji parsera wymaga tylko zmiany właściwości systemowej — kod aplikacji wykorzystuje dostarczoną przez firmę Sun warstwę abstrakcyjną.
Współpraca JAXP-SAX Kiedy korzystamy z interfejsu SAX, powinniśmy skorzystać z klas JAXP SAXParser i SAXParserFactory. Pierwsza przejmuje rolę implementacji parsera SAX, a druga obsługuje dynamiczne ładowanie implementacji. Przed omówieniem tych zmian zobaczymy, w jaki sposób można stworzyć abstrakcję konkretnej implementacji parsera XML (przykład 8.1).
Przykład 8.1. Pobieranie implementacji parsera SAX za pomocą JAXP import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.xml.sax.helpers.HandlerBase; public class JAXPSAXTest { public void doSomeParsing() { SAXParser parser; SAXParserFactory factory = SAXParserFactory.newInstance(); HandlerBase myHandler = new MyHandlerBase(); factory.setValidating(true); factory.setNamespaceAware(true);
}
try { parser = factory.newSAXParser(); parser.parse(myURI, myHandler); } catch (SAXException e) { // Obsługa błędów SAX } catch (IOException e) { // Obsługa błędów związanych z wczytywaniem URI } catch (ParserConfigurationException e) { // Obsługa błędów związanych z niemożnością // załadowania określonej implementacji parsera } }
Przykład nie różni się specjalnie od tych, które widzieliśmy w poprzednich rozdziałach, z tym że nie ma tutaj kodu specyficznego dla Apache Xerces czy jakiegokolwiek innego konkretnego parsera. Klasa SAXParser przechwytuje egzemplarz wykorzystywanej implementacji parsera i pobiera ten egzemplarz z egzemplarza klasy SAXParserFactory. Jedyne, czym powyższy kod się różni od omawianych wcześniej, to fakt, że sprawdzanie poprawności i „świadomość” przestrzeni nazw włączana jest poprzez SAXParserFactory, a nie przez sam egzemplarz parsera.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 178
Parsery i JAXP
179
Różnica polega na tym, że tutaj wszystkie egzemplarze uzyskują podane właściwości; należy więc pamiętać, aby funkcji nie włączać zbyt wcześnie, i zapomnieć, że jest włączona przy pobieraniu implementacji parsera w dalszej części kodu. Inne odstępstwo od kodu, w którym bezpośrednio korzystaliśmy z implementacji parsera SAX, polega na tym, że do metody parser() klasy SAXParser konieczne jest przekazanie egzemplarza klasy pomocniczej HandlerBase. Tak więc wszystkie programy obsługi zawartości, błędów i inne gromadzone są w jednej podklasie HandlerBase. Należy uważać, aby nie implementować bezpośrednio interfejsów SAX i próbować korzystać z nich indywidualnie (poprzez metody setXXXHandler() dostępne w interfejsie SAX Parser). Jeśli klasa HandlerBase z niczym się Czytelnikowi nie kojarzy, to prawdopodobnie Czytelnik nie zna interfejsu SAX 1.0. Niestety, JAXP obsługuje tylko SAX 1.0 — w SAX-ie 2.0 klasę HandlerBase zastąpiono klasą DefaultHandler. W klasie tej zaimplementowano najważniejsze interfejsy SAX 1.0, udostępniając puste implementacje wszystkich metod zdefiniowanych w ErrorHandler, DTDHandler, EntityResolver i DocumentHandler (który w SAX-ie 2.0 zarzucono na rzecz ContentHandler). W podklasie HandlerBase nadpisujemy wszystkie wywołania, w których ma nastąpić jakieś działanie. Po utworzeniu programu obsługi metoda parse() może zostać wywołana na egzemplarzu SAXParser — jako parametry podaje się identyfikator URI dokumentu do przetworzenia oraz egzemplarz DefaultHandler.
Współpraca JAXP-DOM Podstawy współpracy JAXP-a z DOM-em są takie same jak w przypadku współpracy JAXP — SAX. Klasami analogicznymi do SAXParser i SAXParserFactory są DocumentBuilder i DocumentBuilderFactory, służące do tworzenia drzewa modelu DOM (także w pakiecie javax.xml.parsers). Faktycznie obie te klasy wykorzystują do komunikacji z resztą aplikacji interfejsy API SAX — zgłaszają również te same wyjątki co klasy SAX (w tym SAXException). Specyfikacja JAXP nie wymaga, aby implementacje DOMBuilder używały SAX-a do tworzenia drzewa DOM; wymaga za to wykorzystania interfejsu API w komunikacji z aplikacją. Kod wykorzystujący klasy JAXP DOM (przykład 8.2) jest niemal identyczny z kodem poprzedniego przykładu.
Przykład 8.2. Pobieranie implementacji parsera DOM za pomocą JAXP import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.xml.sax.helpers.HandlerBase; public class JAXPDOMTest { public void doSomeParsing() { DocumentBuilder parser; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); try { parser = factory.newDocumentBuilder(); Document doc = parser.parse(myURI);
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 179
Klasa DocumentBuilderFactory umożliwia włączenie sprawdzania poprawności i „świadomości” przestrzeni nazw; ustawienia te zostaną utrzymane dla wszystkich egzemplarzy DocumentBuilder uzyskanych w wyniku działania metody newDocumentBuilder(). Po określeniu położenia dokumentu do przetwarzania można wywołać metodę parse(), zwracającą obiekt DOM Document, będący wynikiem przetwarzania. Po przetworzeniu można wykorzystać standardowe obiekty i metody DOM — aplikacja jest zupełnie oddzielona od detali związanych z parserem określonego producenta.
Wybór parsera Nie powiedzieliśmy jeszcze o sposobie ustalenia, jaki parser ma być zastosowany. Jak wspomnieliśmy, JAXP ma uprościć zmianę implementacji parsera. Jednakże nie jest to czynność tak prosta, jak mogłoby się wydawać. Ponieważ JAXP zawiera cztery klasy abstrakcyjne, każdy parser obsługujący ten interfejs musi udostępniać implementacje klas JAXP. Na przykład Apache Xerces posiada wymagane klasy w org.apache.xerces.jaxp. Specyfikacja JAXP mówi, że każda implementacja może domyślnie udostępniać dowolny parser; innymi słowy, implementacja Apache Xerces udostępnia jako domyślny parser Apache Xerces, a Oracle — najprawdopodobniej parser Oracle. Zmiana domyślnej klasy parsera na inną może zostać wykonana poprzez ustawienie właściwości systemowej javax.xml.parsers.SAXParserFactory tak, by wskazywała na nowego producenta SAX, lub przez ustawienie javax.xml.parsers.DocumentBuilderFactory tak, by wskazywała na nowego producenta DOM. Właściwości systemowe można ustawiać za pomocą opcji -D w programach uruchamianych z wiersza poleceń lub poprzez System.setProperty() w kodzie Javy. Klasy JAXP odczytują właściwości systemowe i odpowiednio reagują na wywołania newSAXParser() i newDocumentBuilder() — udostępniają egzemplarze klas danego producenta. Jednakże większość współczesnych aplikacji nie jest obsługiwana z wiersza poleceń, ale poprzez interfejs WWW; czasem stanowią one część większego pakietu. Co więcej, użycie System.setProperty() w zwyczajny sposób spowodowałoby odczytanie informacji przekazanych do setProperty() (takich jak nazwa własności i klasa sterownika SAX) z pliku właściwości. Plik taki nie może być plikiem XML (o tym więcej w rozdziale 11.), ponieważ nie istnieje jeszcze odpowiedni parser. Pakiet Java Development Kit (JDK) 1.3 udostępnia możliwość określania właściwości we wdrażanym pliku jar; jednak w czasie pisania tej książki wiele popularnych platform (np. Linux) nie obsługiwało jeszcze JDK 1.3. Innymi słowy, duże możliwości konfiguracyjne w ramach JAXP dopiero powstają. Mimo tych niedogodności, koncepcje przyświecające utworzeniu interfejsu JAXP są niezwykle wartościowe; ponadto firma Sun przekazała niedawno kody JAXP i parsera Project X opiekunom projektu Apache Xerces (kod nosi nazwę „Crimson”), co oznacza, że Sun zamierza przyspieszyć rozwój swojego API i że sprzyja otwartym standardom. Pod koniec roku 2000 można oczekiwać
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 180
JDOM — kolejny API?
181
wersji JAXP 1.1, obsługującej DOM Level 2, SAX 2.0 i umożliwiającej bardziej ogólny sposób wyboru parsera. (Wersja robocza specyfikacji JAXP 1.1 została udostępniona w grudniu 2000 r. — przyp. tłum.).
JDOM — kolejny API? Czytelnik poznał już interfejsy API służące do korzystania z XML-a z poziomu Javy; zostały omówione ich największe wady i zalety. Jednak trudno zafascynować się tym, co mają nam do zaoferowania SAX, DOM i JAXP. O ile społeczność XML-a ma już konieczne do pracy narzędzia, o tyle programiści Javy są nieco zdezorientowani niestandardowymi sposobami zachowania SAX-a i DOM-a oraz ogólną trudnością manipulacji dokumentami XML, a nawet po prostu problemami z uzyskaniem parsera! Dlatego, zgodnie z tradycją oprogramowania open source, projektów związanych z językiem Java i wydawnictwa O'Reilly & Associates, postanowiliśmy naprawić ten błąd i zaprezentować nowe rozwiązanie — interfejs JDOM.
Skąd nazwa? Pierwsi testerzy JDOM byli nieco zaskoczeni nazwą — zbliżoną do DOM, który to interfejs jest z natury bardziej pojemny. Ponieważ jednak JDOM po prostu reprezentuje dokument w Javie, nazwa wydaje się odpowiednia. Innymi słowy, została wybrana ze względu na precyzyjne odwzorowanie przeznaczenia interfejsu mimo podobieństwa do nazwy innego API. Co więcej, JDOM jest tylko luźno związany z XML-em. Obsługuje dowolny hierarchiczny format danych i może równie łatwo podlegać serializacji, jak zwracać w wyniku dane XML — za pomocą obiektów OutputStream lub File oraz klas wyjściowych JDOM. Implementacja JDOM org.jdom.input.Builder udostępnia również sprawny sposób tworzenia obiektu JDOM Document; obiekt ten może zostać utworzony z pliku właściwości w niestandardowym formacie lub w formacie XML. JDOM faktycznie reprezentuje dowolny zbiór danych w Javie. Na interfejs JDOM składa się specyfikacja autorstwa Bretta McLaughlina i Jasona Huntera (K&A Software), utworzona przy współpracy Jamesa Duncana Davidsona (autor specyfikacji JAXP). JDOM jest mało wymagającym mechanizmem do analizowania i przeglądania dokumentu XML. Opisano dane wejściowe i wyjściowe służące do stworzenia obiektu JDOM Document z istniejących danych XML i zwrócenia go w określonym celu. Stworzono działającą implementację w postaci pakietu org.jdom (wersja 1.0) — można go pobrać ze strony pod adresem http://www.jdom.org. Celem stworzenia JDOM było rozwiązanie problemów związanych z interfejsami SAX, DOM i JAXP. Zobaczymy, co oferuje nowy interfejs i czy w ogóle był potrzebny (mało jeszcze tych skrótów?). Twórcy JDOM mieli na celu stworzenie skupionego wokół Javy, wysoko wydajnego zamiennika interfejsów SAX i DOM w większości zastosowań1. Nie jest oparty na DOM-ie czy SAX-ie. Pozwala użytkownikowi pracować na dokumencie XML w postaci drzewiastej bez konieczności 1
Oczywiście, w niektórych przypadkach JDOM nie jest dobrym zamiennikiem większego interfejsu DOM; nie jest obsługiwany przez różne języki programowania i nie udostępnia ściśle określonej reprezentacji drzewiastej oferowanej przez DOM. Jednak przynajmniej w 80% przypadków JDOM potrafi rozwiązać problemy programistów związane z obróbką XML-a.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 181
182
Rozdział 8. JDOM
stosowania specyficznych rozwiązań typowych dla DOM-a. Jednocześnie jest tak wydajny jak SAX — przetwarza i zwraca wynik bardzo szybko. Jest „świadomy” przestrzeni nazw, obsługuje sprawdzanie poprawności poprzez DTD (i będzie obsługiwał schematy, gdy specyfikacja XML Schema zostanie sfinalizowana) i nigdy nie zwraca obiektów w formie NodeList lub Attributes; zamiast tego zwracane są klasy zbiorowe Java 2, takie jak List i Map. W pełni obsługiwane są dodatkowe implementacje, ale sam JDOM składa się z konkretnych (nieabstrakcyjnych) klas, a więc do stworzenia elementów, atrybutów, komentarzy i innych konstrukcji JDOM nie jest potrzebna informacja o producencie.
Spowalnianie specyfikacji To dobrze, że standardy takie jak DOM i SAX są tak rzetelnie weryfikowane przez — odpowiednio — konsorcjum W3C oraz Davida Megginsona i społeczność XML. Jednak często pomiędzy opublikowaniem kolejnych wersji mija dużo czasu. Ponadto coraz większe zainteresowanie oprogramowaniem open source i umożliwienie publicznego dostępu (i niejednokrotnie możliwości modyfikacji) kodu wymaga przyspieszenia procesu weryfikacji i tworzenia jak najświeższych specyfikacji związanych z Javą i XML-em. JDOM, będący w całości oprogramowaniem typu open source, to krok w kierunku przyspieszenia wdrażania standardów i specyfikacji. Na przykład JDOM już teraz obsługuje przestrzenie nazw XML we wszystkich obiektach Document (nawet jeśli Document został zbudowany za pomocą parsera nie znającego przestrzeni nazw!). JDOM zostanie również wkrótce przeniesiony do serwera CVS z dostępem publicznym. To umożliwi modyfikowanie kodu, lepsze jego zrozumienie oraz prostsze aktualizacje. JDOM będzie nieustannie ewoluował w kierunku rozwiązania odpowiadającego większości programistów Javy korzystających z XML-a (serwer CVS JDOM-a już działa — przyp. tłum.).
Zoptymalizowany pod kątem Javy Już wspomnieliśmy, że JDOM to pełny interfejs API oparty na Javie 2, korzystający z klas zbiorowych. Na razie nie ma planów przeniesienia JDOM na inny język; nie jest to więc interfejs standardowy w zakresie obsługi wielu języków, ale za to istotnie zwiększa funkcjonalność w samej Javie, a taki właśnie był cel projektu. Podstawowe klasy JDOM oparte są na języku Java 2, co umożliwiło korzystanie z klas Collection i „słabych” odwołań; dostępna jest jednak również wersja JDK 1.1. Interfejs JDOM został zaprojektowany z myślą o programiście. Jest prosty w nauce i w zastosowaniu, ponieważ utrzymuje zgodność ze sprawdzonymi wzorcami projektowymi Javy. Konstrukcje JDOM (elementy, komentarze, atrybuty itd.) tworzone są w wyniku budowania egzemplarzy obiektów. Dokument XML (jak i dowolny inny) może być postrzegany jako jedna całość i każdy element dokumentu jest przez cały czas dostępny. Tworzenie, usuwanie i modyfikację fragmentów danych XML przeprowadza się za pomocą prostych metod. Obsługę wejścia i wyjścia zapewniają klasy Javy (URL, InputStream, OutputStream, File itd.). Próba unowocześnienia interfejsów SAX i DOM zabrałaby z pewnością więcej czasu aniżeli stworzenie zupełnie nowego rozwiązania. W pozostałej części rozdziału zostanie omówione to właśnie nowe rozwiązanie — Czytelnik dowie się, jak korzystać z niego w celu manipulacji danymi XML z poziomu Javy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 182
Uzyskiwanie dokumentu
183
Uzyskiwanie dokumentu Pierwszym zadaniem, jakie zawsze należy wykonać przy korzystaniu z JDOM-a, jest uzyskanie obiektu JDOM Document. Obiekt ten stanowi podstawową klasę JDOM, reprezentującą dokument XML. Jak wszystkie inne obiekty w modelu JDOM, klasa org.jdom.Document została opisana w dodatku A; wymieniono tam również sygnatury metod. Pod adresem http:// www.jdom.org dostępna jest też pełna dokumentacja Javadoc.
Istnieją dwa sposoby uzyskania obiektu JDOM Document: utworzenie obiektu od podstaw (kiedy nie ma potrzeby odczytywania istniejących danych XML) lub zbudowanie go z istniejących danych XML.
Tworzenie dokumentu od podstaw Kiedy nie ma potrzeby pobierania istniejących danych, tworzenie dokumentu JDOM Document ogranicza się do wywołania konstruktora: Document doc = new Document(new Element("root));
Jak wspomnieliśmy wcześniej, JDOM to zestaw konkretnych klas, a nie interfejsów. Nie trzeba więc korzystać z bardziej zawiłego kodu związanego z konkretnymi producentami i koniecznego do stworzenia obiektu org.w3c.dom.Element. Wystarczy wykonać operację new na obiekcie Document — powstaje poprawny obiekt JDOM Document, gotowy do użytkowania. Document nie jest też związany z żadnym konkretnym parserem. Dane XML często tworzy się z pustego szablonu, a nie z istniejących danych i JDOM udostępnia nam w tym zakresie konstruktor dla org.jdom.Document, wymagający tylko podania głównego elementu Element jako parametru. W przykładzie 8.3 powstaje właśnie taki dokument XML od podstaw. import org.jdom.Document; import org.jdom.Element; /** *
* Tworzenie obiektu JDOM Document od zera *
* * @version 1.0 */ public class FromScratch { /** *
* Tworzymy prosty dokument XML w pamięci *
*/ public static void main(String[] args) { Document doc = new Document(new Element("root")); System.out.println("Dokument został utworzony"); } }
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 183
184
Rozdział 8. JDOM
Ten fragment kodu tworzy nowy obiekt JDOM Document z elementem głównym (Element o nazwie „root”). Document ten może potem zostać przetworzony w pamięci i wyprowadzony na strumień wyjściowy.
Budowanie obiektu Document z XML-a Częściej jednak korzystamy z pewnych danych wejściowych i dopiero te przekształcamy w obiekt Document. Jest to równie proste. Ponieważ dokumenty JDOM mogą powstawać z wielu źródeł, udostępniony został oddzielny pakiet z klasami tworzącymi obiekt JDOM Document z różnych formatów wejściowych. W pakiecie tym, org.jdom.input, zdefiniowano interfejs Builder, którego metody przedstawione są w przykładzie 8.4.
Przykład 8.4. Interfejs org.jdom.input.Builder public interface Builder { // Tworzenie obiektu JDOM Document z InputStream public Document build(InputStream in) throws JDOMException; // Tworzenie obiektu JDOM Document z File public Document build(File file) throws JDOMException; // Tworzenie obiektu JDOM Document z URL public Document build(URL url) throws JDOMException; }
Programista otrzymuje mechanizm do tworzenia obiektu JDOM Document na podstawie różnych źródeł i budowania różnych implementacji dla różnych formatów wejściowych. Obecnie JDOM udostępnia dwie takie implementacje, SAXBuilder i DOMBuilder2. Umożliwiają one wykorzystanie istniejących obecnie standardowych parserów bez konieczności wprowadzania w nich żadnych zmian.
SAXBuilder Budowanie dokumentu JDOM z istniejącego źródła danych XML za pomocą klasy org.jdom.input.SAXBuilder jest całkiem proste. Konstruktor SAXBuilder pobiera dwa opcjonalne parametry — nazwę klasy parsera SAX do wykorzystania (powinna ona implementować org.xml.sax.XMLReader) oraz znacznik informujący, czy ma nastąpić sprawdzanie poprawności. Jeśli żadnego parametru nie podano, wykorzystywany jest parser domyślny (obecnie Apache Xerces), a sprawdzanie poprawności nie odbywa się. Stwórzmy prostą klasę SAXTest, pobierającą plik z wiersza poleceń i tworzącą obiekt JDOM Document na podstawie pliku i za pomocą klasy SAXBuilder: import java.io.File; import org.jdom.Document; import org.jdom.Element; 2
Być może w czasie, kiedy książka trafi do rąk Czytelników, w interfejsie JDOM 1.0 będą istniały dodatkowe implementacje Builder. Podstawowy interfejs 1.0 jest już „zamrożony”, ale pakiety pomocnicze (org.jdom.input, org. jdom.output i org.jdom.adapters) — nie. Możliwe jest więc, że w czasie powstawania książki pojawią się nowe implementacje (w czasie tłumaczenia, w styczniu 2001 r., wciąż istniały tylko dwie wspomniane implementacje interfejsu Builder — przyp. tłum.).
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 184
Uzyskiwanie dokumentu
185
import org.jdom.JDOMException; import org.jdom.input.Builder; import org.jdom.input.SAXBuilder; public class SAXTest { public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: SAXTest [plik do przetworzenia]"); return; } try { // Budowanie dokumentu bez sprawdzania poprawności Builder builder = new SAXBuilder(false); Document doc = builder.build(new File(args[0])); System.out.println("Dokument został odczytany"); } catch (JDOMException e) { e.printStackTrace(); } } }
Proste, prawda? SAXBuilder obsługuje wszystkie zawiłości związane z tworzeniem różnych klas procedur obsługi SAX, rejestrowaniem ich w implementacji XMLReader i budowaniem obiektu JDOM Document. Przypomina to sposób, w jaki wiele parserów DOM buduje obiekt DOM Document za pomocą parsera SAX; jednak w celu uproszczenia interfejs SAXBuilder w JDOM-ie obsługuje wszystkie wyjątki SAXException i konwertuje je na JDOMException. Kod SAX pozostaje odizolowany od procesu tworzenia obiektu Document, a JDOM gwarantuje, że przekonwertowane wyjątki zawierają informacje o specyficznych problemach, jakie wystąpiły w procesie przetwarzania (wraz z wierszem, w którym wystąpiły).
DOMBuilder Klasa org.jdom.input.DOMBuilder działa niemal tak samo jak SAXBuilder. Z jej pomocą również możemy uzyskać obiekt JDOM Document, ale tym razem za pomocą modelu DOM i parsera DOM. Ponieważ specyfikacja DOM nie definiuje standardu interfejsu parsera, stworzono pakiet org.jdom.adapters, który udostępnia abstrakcyjną warstwę dla parserów określonych producentów i tym samym sposób utworzenia obiektu DOM Document. Konstruktor DOMBuilder pobiera znacznik wskazujący, czy ma nastąpić sprawdzanie poprawności, oraz nazwę klasy adaptera. Może to być dowolna klasa (w tym zdefiniowana przez użytkownika), o ile tylko implementuje interfejs DOMAdapter zdefiniowany w org.jdom.adapters. Po stworzeniu DOMBuilder działa dokładnie tak jak SAXBuilder. Oto sposób tworzenia obiektu Document za pomocą interfejsu DOM: import java.io.File; import import import import import
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 185
186
Rozdział 8. JDOM public class DOMTest { public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: DOMTest [plik do przetworzenia]"); return; } try { // Budowanie dokumentu bez sprawdzania poprawności, parser Oracle Builder builder = new DOMBuilder("org.jdom.adapters.OracleV2DOMAdapter"); Document doc = builder.build(new File(args[0])); System.out.println("Dokument został odczytany"); } catch (JDOMException e) { e.printStackTrace(); } } }
Pakiet org.jdom.adapters został utworzony na potrzeby interfejsu JDOM, ale może być również wykorzystany w aplikacjach korzystających tylko z DOM-a, jako elastyczna alternatywa dla JAXP-a. Oferuje on pełne oddzielenie od procesu przetwarzania DOM i umożliwia dostarczenie danych wejściowych jako InputStream lub w postaci nazwy pliku. Udostępnia także znacznik informujący o sprawdzaniu poprawności. W wyniku działania pakietu zwracany jest gotowy obiekt DOM Document. Innymi słowy, tych klas można użyć w aplikacji w celu zapobieżenia konieczności importowania specyficznych dla DOM-a implementacji parsera. Co więcej, klasy adaptera wykorzystują refleksję, a więc nie wymagają obecności implementacji parsera w ścieżce dostępu do klas w czasie kompilacji. To zapewnia całkowitą konfigurowalność i przenośność aplikacji, jeśli chodzi o zastosowany parser DOM.
Drzewo DOM tworzone jest za pomocą parsera Oracle V2 XML. Następnie w oparciu o to drzewo budowany jest obiekt JDOM Document. Aby wykorzystać parser domyślny, wystarczy wywołać Builder builder = new DOMBuilder(). Wszystkie obecne implementacje parsera DOM do tworzenia drzewa DOM faktycznie wykorzystują SAX. Dlatego korzystanie z interfejsu DOMBuilder w JDOM nie ma raczej sensu; zawsze proces ten będzie wolniejszy niż w przypadku zastosowania SAXBuilder (w obu przypadkach korzystamy z SAX-a), zawsze pochłonie też więcej pamięci, ponieważ na czas konwersji na format JDOM musi w niej powstać pełny obraz drzewa DOM. DOMBuilder nie powinien być więc często wykorzystywany. Jego zasadnicza wartość leży w metodzie, jaką udostępnia do tworzenia obiektu JDOM Document z istniejącego drzewa DOM (np. uzyskanego jako dane wejściowe aplikacji z innej aplikacji, nie obsługującej JDOM-a). Metoda ta, build(org.w3c.dom.Document), jest opisana szczegółowo w dodatku A.
Po utworzeniu dokumentu JDOM Document dalsze działanie programu przebiega w zwykły sposób.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 186
Korzystanie z obiektu Document
187
Korzystanie z obiektu Document Kiedy mamy już obiekt Document (uzyskany albo poprzez bezpośrednie stworzenie egzemplarza, albo poprzez wykorzystanie którejś z klas wejściowych), możemy wykonywać na nim operacje niezależnie od formatu czy interfejsu API. Nie ma dowiązań do SAX-a, DOM-a czy do oryginalnego formatu danych. Jak zobaczymy dalej, nie ma także sprzężenia z formatem wyjściowym. Dowolny obiekt JDOM Document może na wyjściu przybrać postać dowolnego pożądanego formatu! Sam obiekt Document posiada metody do obsługi czterech składników, jakie może przyjąć: DocType (określa zewnętrzną DTD lub udostępnia definicje wewnętrzne), ProcessingInstruction, Element główny i Comment. Każdy z tych obiektów odwzorowuje jakiś fragment XML-a. W ten sposób powstaje reprezentacja tych konstrukcji XML w Javie.
Obiekt DocType Obiekt JDOM DocType to prosta reprezentacja deklaracji DOCTYPE dokumentu XML. Załóżmy, że mamy następujący plik XHTML:
. Komentarze w JDOM reprezentowane są przez klasę Comment; egzemplarze tworzone są na poziomie dokumentu lub jako elementy potomne. Innymi słowy, zarówno obiekt JDOM Document, jak i elementy mogą posiadać komentarze. Aby uzyskać komentarze dokumentu, należy zastosować getContent(), zwracającą listę zawierającą wszystkie obiekty Comment dokumentu, jak również element główny. Komentarze umieszczone przed elementem głównym pojawią się na liście przed elementem głównym; a te po nim — znajdą się w dalszym miejscu listy. Aby uzyskać komentarze dla danego Elementu, należy wywołać metodę getMixedContent(), która zwraca wszystkie obiekty Comment, Element i String (dane tekstowe), zagnieżdżone wewnątrz elementu i w takiej kolejności, w jakiej występują w dokumencie. Załóżmy, że mamy następujący plik XML:
Czytamy sobie spis treści!
Zazwyczaj komentarze nie są używane przez aplikację. Gdyby jednak zaszła taka potrzeba, poniższy kod pozwoli nam je „wyciągnąć” z dokumentu: List docContent = doc.getContent(); List elemContent = root.getMixedContent(); for (int i=0, size=docContent.size(); i
Java i XML
Wprowadzenie ...
Taki wynik został uzyskany po uruchomieniu programu testującego na pliku contents.xml, utworzonym we wcześniejszych rozdziałach. Element JavaXML:Dodatkowe (i elementy potomne) został opatrzony komentarzami, ponieważ definicja DTD nie pozwala na jego istnienie. Jeśli nie byłoby komentarzy, program zgłosiłby następujący komunikat o błędzie (kiedy przy tworzeniu egzemplarza SAXBuilder lub DOMBuilder zażądano by sprawdzania poprawności): org.jdom.JDOMException: Error in building from stream: Error on line 59 of XML document: Element type "JavaXML:Dodatkowe" must be declared. at org.jdom.input.DOMBuilder.build(DOMBuilder.java, Compiled Code) at org.jdom.input.DOMBuilder.build(DOMBuilder.java, Compiled Code) at JDOMTest.domDocument(JDOMTest.java, Compiled Code) at JDOMTest.main(JDOMTest.java, Compiled Code) Error on line 59 of XML document: Element type "JavaXML:Dodatkowe" must be declared.
Jedną z istotnych cech interfejsu JDOM jest diagnozowanie błędów — powyższy komunikat dostarcza szczegółowych informacji o błędach w danych wejściowych XML i tym samym upraszcza sprawdzanie ich poprawności. Ponieważ metody wejścia i wyjścia zajmują tylko cztery wiersze kodu (patrz domDocument() i saxDocument()), JDOM może zostać wykorzystany właśnie do sprawdzania poprawności i formatowania dokumentów XML. Nadeszła pora, aby przypomnieć sobie programy SAXParserDemo i DOMParserDemo z rozdziałów 3. i 7. Oba te programy wyswietlały dokumenty XML pobrane z pliku wejściowego; SAXParserDemo udostępniał podgląd samego procesu przetwarzania, zaś DOMParserDemo to właściwie klasa „ładnie drukująca” dokument XML. Przykład 8.7 to kod źródłowy com.oreilly.xml.PrettyPrinter, klasy narzędziowej wykonującej to samo zadanie co DOMParserDemo, ale poprzez JDOM.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 200
Dokument wyjściowy
Uważny Czytelnik mógł zauważyć, że w powyższym kodzie nie zajęliśmy się sprawdzaniem poprawności ani przestrzeniami nazw. Ponieważ JDOM obsługuje przestrzenie nazw wewnętrznie (a nie polegając na informacjach dostarczanych przez DOM Level 2 czy SAX 2.0), sprawdzanie poprawności odbywa się przy jednoczesnej obsłudze przestrzeni nazw. JDOM faktycznie wyłącza „świadomość” przestrzeni nazw w klasach SAXBuilder i DOMBuilder! Nie tylko umożliwia to przeprowadzenie sprawdzania poprawności, ale także przyspiesza przetwarzanie dokumentów XML.
Przykład 8.7. Klasa narzędziowa com.oreilly.xml.PrettyPrinter package com.oreilly.xml; import java.io.File; import import import import
/** * PrettyPrinter wyświetla dokument XML spod podanego URI * * @author Brett McLaughlin * @author Jason Hunter * @version 1.0 */ public class PrettyPrinter { /** *
* Ładnie drukuje XML danego URI *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: " + "java com.oreilly.xml.PrettyPrinter [XML_URI]"); return; } String filename = args[0]; try { // Budowanie dokumentu z użyciem SAX i Xerces, bez sprawdzania poprawności Builder builder = new SAXBuilder(); // Tworzenie dokumentu (ze sprawdzaniem poprawności) Document doc = builder.build(new File(filename)); // Wyświetlenie dokumentu, standardowy program formatujący XMLOutputter fmt = new XMLOutputter(); fmt.output(doc, System.out); } catch (Exception e) { e.printStackTrace(); } } }
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 201
201
202
Rozdział 8. JDOM
Szybki przegląd interfejsu JDOM mamy już za sobą. Czytelnik poznał jedynie ułamek jego możliwości. Pełna dokumentacja znajduje się w dodatku A — opisane są tam klasy i interfejsy DOM oraz dostępne w nich metody. Opisano tam również pakiety dodatkowe dla JDOM-a, org. jdom.adapters, org.jdom.input i org.jdom.output. Interfejsu JDOM będziemy używali w przykładach w pozostałej części książki. Wielokrotnie powrócimy też do różnic pomiędzy interfejsami JDOM, SAX i DOM, aby w razie potrzeby Czytelnik umiał skorzystać z dowolnego z nich i aby na koniec sam wyrobił sobie opinię na temat ich przydatności. Najnowsza wersja JDOM i odpowiadająca jej dokumentacja Javadoc znajduje się pod adresami: http://www.jdom.org i http://www.newInstance.com.
Co dalej? W niniejszym rozdziale Czytelnik poznał interfejs JDOM. Znając również JAXP, może przystąpić już do tworzenia specyficznych aplikacji korzystających z XML-a. Nadal korzystając z interfejsów SAX, DOM i JDOM, w drugiej połowie książki omówimy struktury publikacji WWW, aplikacje typu firma-firma, XML-RPC, Rich Site Summary (RSS), konfiguracje XML i szereg spokrewnionych z nimi zagadnień. Poznawane wiadomości pozwolą Czytelnikowi tworzyć nowe, bardziej efektywne aplikacje.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 202
9 Struktury publikacji WWW Czytelnik poznał już podstawy korzystania z XML-a z poziomu języka Java. Omówione zostały interfejsy SAX i DOM, umożliwiające manipulację i tworzenie danych XML. Czytelnik poznał również JDOM, udostępniający bardziej naturalny dla programistów Javy sposób korzystania z danych XML. Skoro Czytelnik wie już, jak używać języka XML z poziomu programów, pora zająć się konkretnymi aplikacjami. W następnych sześciu rozdziałach przedstawione zostaną najistotniejsze zastosowania języka XML i, co najważniejsze, omówione zostaną sposoby implementowania tych aplikacji w Javie. Są dosłownie setki, a wkrótce będą tysiące ważnych zastosowań języka XML, ale tutaj opiszemy tylko te najważniejsze — te, które mogą zrewolucjonizować proces tworzenia oprogramowania. Jako pierwsze zostaną omówione aplikacje będące przedmiotem najgorętszych dyskusji w społecznościach XML-a i Javy — struktury publikacji WWW. Jak to już zostało wspomniane, możliwość tworzenia prezentacji z zawartości jest raczej przereklamowana — dużo ważniejsza jest przenośność danych oferowana przez XML. Nie zmienia to jednak faktu, że XML świetnie nadaje się również do generowania wartości prezentacyjnej. Jest to cecha szczególnie istotna w aplikacjach WWW. Za pięć lat praktycznie dowolna duża aplikacja będzie albo całkowicie wykorzystywała możliwości WWW, albo będzie przynajmniej udostępniała „webowy” interfejs. Użytkownicy domagają się coraz bogatszych zestawów funkcji, a działy marketingowe — coraz większej elastyczności w wyglądzie i zachowaniu aplikacji. Powstał nowy zawód — artysta WWW; to nie to samo co webmaster — nie musi dobrze znać Perla, ASP, JavaScriptu czy innych języków skryptowych. Cały dzień nic nie robi, tylko tworzy, modyfikuje i projektuje dokumenty HTML. Gwałtowne zmiany na rynku wymagają często kompletnej przebudowy witryny nawet raz w tygodniu, więc takie stanowisko jest bardzo potrzebne. Style kaskadowe (CSS) okazały się tutaj bardzo pomocne, ale i tak utrzymanie spójności wielu stron wymaga bardzo dużo czasu. Taką sytuację na siłę można by zaakceptować, ale na pewno żaden programista nie chciałby spędzić całego życia na wprowadzaniu zmian w stronach HTML. Wraz z popularyzacją programów Javy uruchamianych po stronie serwera, ten problemstał się bardziej dokuczliwy. Twórcy serwletów długie godziny spędzają wyłącznie na poprawianiu instrukcji out.println() zwracających dane HTML. Ta sytuacja doprowadziła do powstania
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 203
204
Rozdział 9. Struktury publikacji WWW
specyfikacji Java Server Pages (JSP); jednakże JSP nie stanowi rozwiązania — przenosi jedynie ciężar na programistę HTML-a, który cały czas musi unikać wprowadzania zmian w zagnieżdżonym w stronach kodzie Javy. Co więcej, w JSP nie ma obiecanego jednoznacznego oddzielenia warstwy danych i prezentacji. Oczekiwano więc sposobu generowania czystych danych i — później — nakładania jednolitego stylu o określonej porze (generowanie statyczne) lub bezpośrednio w czasie działania (dynamiczne generowanie zawartości). Czytelnik już wie, że powyższe problemy rozwiązują technologie XSL i XSLT. Jest jednak pewien problem — aby generować zawartość, szczególnie dynamicznie, trzeba dysponować odpowiednim mechanizmem. Co z tego, że na serwerze mamy setki dokumentów XML, jeśli nie ma mechanizmu umożliwiającego przekształcenie ich do potrzebnej postaci. Jeśli dodamy do tego potrzebę istnienia serwletów i innych elementów znajdujących się po stronie serwera, umożliwiają tworzenie wynikowych danych XML, to otrzymujemy zespół wymagań określający właśnie strukturę publikacji WWW. W niniejszym rozdziale przyjrzymy się takim właśnie strukturom. Czytelnik będzie mógł przekonać się, że dzięki nim zniknie potrzeba „ślęczenia nad HTML-em”. „Artyści WWW” staną się specjalistami od XML-a i XSL-a, co na pewno wyjdzie im na dobre, zaś wygląd i zachowanie aplikacji będzie można zmienić w dowolnej chwili i w dowolny sposób. Struktura publikacji WWW (ang. web publishing framework) stanowi rozwiązanie wszystkich opisywanych wyżej problemów. Tak jak serwer WWW odpowiada na żądanie pobrania pliku, struktura publikacji WWW odpowiada na żądanie publikowanej wersji pliku. Publikowany plik (ang. published file) to plik przekształcony za pomocą XSLT, stworzony na poziomie aplikacji lub przekształcony z innego formatu (np. PDF). Żądający nie widzi danych, z których powstał taki plik; nie musi również jawnie żądać wykonania publikacji. Często z identyfikatora URI (np. http://naszHost.com/publish) wynika, że w serwerze WWW uruchomiony jest mechanizm publikacji. Jak można podejrzewać, sama koncepcja jest dużo prostsza niż faktyczna implementacja, a wybranie odpowiedniej struktury publikacji dla własnych potrzeb to wcale nie łatwe zadanie.
Wybór struktury Czytelnik z pewnością zaczyna już doceniać znaczenie struktur publikacji. Rozwiązań jest jednak bardzo wiele. Język Java oferuje prosty sposób współpracy z rozmaitymi narzędziami XML wykorzystywanymi w strukturach publikacji. Serwlety Javy umożliwiają natomiast prostą obsługę żądań i odpowiedzi WWW. Ale lista struktur publikacji jest krótka, a lista tych dobrych i stabilnych — jeszcze krótsza. Jednym z najlepszych zasobów informacji o tego typu oprogramowaniu jest lista XML Software dostępna pod adresem http://xmlsoftware.com/publishing. Ta lista zmienia się tak często, że nie warto jej tutaj przytaczać. Tym niemniej istnieje szereg istotnych kryteriów, którymi programista powinien się kierować przy wyborze struktury publikacji — i właśnie one zostaną tutaj omówione.
Stabilność Obecnie programiści mają problemy ze znalezieniem produktu w wersji o numerze wyższym niż 2.x. Mało tego — ze świecą trzeba szukać struktury publikacji nawet drugiej generacji! Oczywiście, wyższy numer wersji nie jest jeszcze gwarancją stabilności, ale często odzwierciedla ilość czasu, wysiłków i rzetelność testowania, jakiemu poddano daną strukturę. System publikacji XML to na tyle nowa technologia, że rynek zalewany jest produktami w wersjach 1.0 i 1.1 — często po prostu niewystarczająco stabilnymi do praktycznego użycia.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 204
Wybór struktury
205
O stabilności produktu mogą również świadczyć inne, stabilne produkty tego samego producenta. Często producent wypuszcza cały pakiet narzędzi; jeśli inne narzędzia nie obsługują interfejsów SAX 2.0 i DOM Level 2 albo wszystkie są w wersjach 1.0 lub 1.1, to strukturę publikacji pochodzącą z takiego pakietu najlepiej na razie wykluczyć z wyboru i poczekać, aż nieco „dojrzeje” i będzie zgodna z nowszymi standardami XML. Należy również unikać technologii przeznaczonych dla konkretnej platformy — jeśli struktura publikacji została scalona z konkretnym systemem (np. Windows), to nie jest to już „czyste” rozwiązanie w Javie. Pamiętajmy, że struktura publikacji ma obsługiwać klientów ze wszystkich platform; dlaczego więc mamy zadowalać się produktem, który nie może zostać uruchomiony na dowolnej platformie?
Integracja z innymi narzędziami i interfejsami XML Oprócz stabilności, struktura publikacji musi również obsługiwać różne parsery i procesory XML. Jeśli jest związana z konkretnym parserem lub procesorem, to tak naprawdę jest to XML-owa wersja Microsoftu — ograniczamy się do jednej konkretnej implementacji technologii. Struktury publikacji mogą dobrze „integrować się” z parserem konkretnego producenta, ale zawsze należy sprawdzić, czy w razie potrzeby parser można zmienić. Jeśli mamy jakiś ulubiony procesor (albo musimy korzystać z procesora przejętego po kimś razem z projektem), trzeba upewnić się, czy struktura z nim współpracuje. Obsługa interfejsów SAX i DOM to bezwzględna konieczność. Warto też szukać takiej struktury, której producent monitoruje specyfikacje XML Schema, XLink, XPointer i inne technologie XML. Od takiego producenta można oczekiwać poprawek struktury mających na celu obsłużenie tych nowych technologii; ponadto mamy gwarancję, że takiej publikacji nie będziemy musieli zmieniać przez długi czas. Należy śmiało pytać o to, kiedy zostanie uruchomiona obsługa nowej specyfikacji.
Wdrożenia w środowiskach produkcyjnych Jedną z najważniejszych przesłanek ułatwiających wybór struktury publikacji jest fakt zastosowania jej w aplikacjach produkcyjnych. Jeśli producent nie potrafi pokazać nam przynajmniej kilku aplikacji referencyjnych lub serwisów wykorzystujących określoną strukturę, to całkiem możliwe, że... po prostu takie nie istnieją. Producenci (oraz programiści z „branży” open source) uwielbiają chwalić się miejscami, w których ich aplikację można zobaczyć w działaniu. Jeśli wyczuwamy tutaj pewne wahanie, to być może aplikacja jest jeszcze zupełnie pionierska — a przecież nie o to nam chodzi.
Podejmujemy decyzję Kiedy już Czytelnik rozpatrzy wszystkie powyższe kryteria, wybór struktury publikacji nie będzie szczególnie trudny — tylko niewiele rozwiązań jest zgodnych z postawionymi wymaganiami, a dochodzą przecież jeszcze wymogi naszej konkretnej aplikacji. W czasie pisania tej książki struktury publikacji obsługujące najświeższe wersje SAX-a, DOM-a i JAXP-a, wdrożone przynajmniej w jednej aplikacji produkcyjnej i mające za sobą przynajmniej trzy znaczące poprawki kodu, można było policzyć na palcach jednej ręki. Nie wymieniamy ich tutaj, bo — co tu dużo mówić — po pół roku mogą już nie istnieć, mogą też zostać zupełnie zmienione. Krajobraz struktur publikacji zmienia się tak gwałtownie, że próba rekomendacji tego czy tamtego produktu może raczej przeszkodzić niż pomóc. Jednakże jedna struktura publikacji nieustannie się sprawdza i jest szczególnie doceniana przez programistów Javy i XML-a. Struktura ta jest szczególnie istotna dla programistów pracujących na zasadach open source oraz piszących w Javie. Chodzi o projekt Apache Cocoon, zapoczątkowany
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 205
206
Rozdział 9. Struktury publikacji WWW
przez Stefano Mazzocchiego — od samego „poczęcia” niezwykle solidną strukturę publikacji. Została opracowana dość dawno, kiedy większość z nas próbowała rozgryźć, co to w ogóle jest ten XML. Cocoon, struktura publikacji oparta w całości na Javie, wchodzi już w drugą fazę rozwoju. Stanowi część projektu Apache XML i domyślnie obsługuje Apache Xerces i Apache Xalan. Pozwala na użycie dowolnego standardowego parsera XML i wykorzystuje niezwykle popularną architekturę serwletów Javy. Jest wykorzystywana w wielu serwisach produkcyjnych (w wersji 1.x) i sprawuje się świetnie. Dlatego właśnie — a także znów konsekwentnie wybierając oprogramowanie open source — w tej książce wybieramy Apache Cocoon. W poprzednich rozdziałach kwestia wyboru parsera i procesora XML była w znacznym stopniu otwartą; przykłady wymagałyby tylko niewielkich modyfikacji w przypadku korzystania z innych implementacji. Jednakże struktury publikacji WWW nie zostały znormalizowane — każda z nich implementuje różne funkcje i konwencje. Dlatego opisane w niniejszym rozdziale przykłady wykorzystujące Apache Cocoon nie są przenośne; jednakże popularność koncepcji i wzorców projektowych zastosowanych w Apache Cocoon usprawiedliwiają poświęcenie całego rozdziału tej właśnie strukturze. Jeśli Czytelnik wybiera inną strukturę publikacji, to powinien przynajmniej przejrzeć opisane tutaj przykłady — ogólne koncepcje są na pewno przenośne, nawet jeśli kod będzie działał tylko w jednej implementacji.
Instalacja We wcześniejszych rozdziałach instrukcja instalacji ograniczała się do wskazania strony WWW, z której należało pobrać oprogramowanie, oraz do przypomnienia o konieczności dodania odpowiedniego pliku jar do ścieżki dostępu do klas. Instalacja struktury publikacji, takiej jak Cocoon, nie jest już tak banalnym zadaniem, dlatego cała procedura została opisana szczegółowo. Jeśli chcemy uzyskać najświeższą wersję struktury, powinniśmy pobrać ją z systemu współbieżnych wersji CVS. Będzie to kod prosto z repozytorium kodów źródłowych, a nie publikowana rzadziej wersja „oficjalna”. Oprogramowanie CVS można pobrać spod adresu http://www.cyclic.com/cyclic-pages/howget.html. W tej książce zajmiemy się wersją 1.x struktury Cocoon. Kiedy książka będzie w sprzedaży, wersja beta 2.0 będzie już prawdopodobnie dostępna (według informacji na stronie http://xml.apache.org/cocoon/cocoon2.html w czasie tłumaczenia książki Cocoon 2.0 był wciąż w fazie „alfa” i twórcy stanowczo odradzali jego użycie w aplikacjach produkcyjnych — przyp. tłum.); jednak z powodu dużych zmian planowanych w tej nowej generacji tutaj skupimy się na wersjach 1.x, obecnie używanych najpowszechniej. Na końcu rozdziału zostaną pokrótce omówione nowe funkcje, jakie pojawią się w Cocoon 2. Jeśli w czasie instalacji czytelnik napotka problemy, może skorzystać z zasobów online. Projekt Cocoon gości na stronach projektu Apache XML, pod adresem http://xml.apache.org. Dostępne są tam listy adresowe (http://xml.apache.org/mail.html), a także bardzo przydatne odpowiedzi na najczęściej zadawane pytania FAQ (http://xml.apache.org/cocoon/faqs.html). Nie trzeba obawiać się zadawania pytań — instalacja złożonych struktur publikacji to niełatwe zadanie, a zawsze istnieje duża szansa, że ktoś już przez to przebrnął i może służyć radą.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 206
Instalacja
207
Korzystanie z narzędzia Ant Weterani systemów uniksowych i Linuksa znają na pamięć poniższe polecenia wykonywane przy kompilacji oprogramowania: /home/bmclaugh (mejis)> ./configure /home/bmclaugh (mejis)> make /home/bmclaugh (mejis)> make install
Współpraca kodu źródłowego i programów make oraz autoconf ma długie tradycje. Ale make nie współpracuje zbyt dobrze z kodem w Javie — użytkownicy Windows muszą korzystać z dodatkowych narzędzi przy kompilacji na tej platformie; uruchomienie dokumentacji Javadoc i innych dodatkowych poleceń wymaga długiego procesu konfiguracji; kompilacja RMI (rmic) jest także złożona itd. Rozwiązanie świetnie spisujące się w przypadku Perla, skryptów powłoki czy kodu C, w zetknięciu z Javą traci na użyteczności. Na szczęście James Duncan Davidson (związany z projektami Jakarta, JAXP i specyfikacją serwletów) nie poprzestał na narzekaniu na make. Zapoczątkował projekt znany obecnie pod nazwą Ant i stanowiący część projektu Apache Jakarta. Ant to narzędzie do budowania oprogramowania oparte na Javie. Jego konfiguracja oparta jest na XML-u i jest to narzędzie niezależne od platformy. Kompilacje RMI, Javadoc, polecenia zewnętrzne — wszystko to jest obsługiwane w tym jednym środowisku. Do „zbudowania” źródeł Cocoon posłuży nam właśnie Ant. Nowa wersja narzędzia Ant została zawarta w pakiecie Cocoon, w katalogu lib/. Ant można również pobrać ze strony Jakarty, http://jakarta.apache.org. Sposób użycia Anta w przypadku oprogramowania Cocoon został zawarty w tym ostatnim; informacje bardziej ogólne znaleźć można pod wymienionym adresem projektu Jakarta.
Uzyskanie pakietu Cocoon Kiedy narzędzie Ant mamy już pod ręką, możemy pobrać źródła Cocoon 1.x. Oprócz możliwości pobrania ich ze stron projektu Apache XML (http://xml.apache.org), najświeższą wersję, wyposażoną we wszystkie nowe funkcje, znaleźć można także w systemie CVS. Jeśli Czytelnik dopiero zaczyna używać oprogramowania Cocoon, może skorzystać z pakietu przygotowanego do pobrania; jednak teraz już Czytelnik zna kod Javy i XML-a, więc może warto pobrać zupełnie najświeższą wersję 1.x.dev z repozytorium CVS. Można to zrobić w następujący sposób: cvs -d :pserver:[email protected]:/home/cvspublic login Password: ******* (Haslo: 'anoncvs') cvs -d :pserver:[email protected]:/home/cvspublic checkout xml-cocoon
W katalogu xml-cocoon pojawi się najnowsza wersja oprogramowania Cocoon. Zawiera ona plik sterujący kompilacją przez Anta, wszystkie wymagane biblioteki i właściwe źródła. Wystarczy teraz przejść do tego katalogu i już można rozpocząć budowanie struktury Cocoon.
Budowanie struktury Cocoon Znajdujemy się w katalogu głównym oprogramowania Cocoon. Aby zbudować strukturę w systemie Windows, należy wpisać następujące polecenie: D:\dev\xml-cocoon> build.bat
natomiast w systemach Unix i Linux: $ sh build.sh
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 207
208
Rozdział 9. Struktury publikacji WWW
Podkatalog lib/ zawiera wszystkie biblioteki konieczne do zbudowania projektu Cocoon. Skrypty instalacyjne dodadzą każdy plik jar tego katalogu do ścieżki dostępu do klas, zawierającej najnowsze wersje Apache Xerces, Apache Xalan i innych programów niezbędnych do prawidłowego działania Cocoon 1.x. Nawet jeśli Czytelnik już ma te biblioteki (Xerces czy Xalan), zalecane jest zastosowanie tych dostarczonych z Cocoon — producent gwarantuje ich współpracę z wersją pobraną z CVS-u. Po skończeniu instalacji ścieżka dostępu do klas będzie obejmowała następujące biblioteki: • JDK Tools: tools.jar • Jakarta Ant: ant.jar • Servlet API 2.2: servlet_2_2.jar • Apache Xerces: xerces_x_y_z.jar • Apache Xalan: xalan_x_y_z.jar • Apache FOP1: fop_x_y_z.jar • Apache Stylebook2: stylebook-x.y-z.jar Skrypt budujący informuje następnie narzędzie Ant, że projekt ma zostać zainstalowany z wykorzystaniem pliku build.xml, znajdującego się w bieżącym katalogu. Po jego wykonaniu Czytelnik powinien uzyskać następujący wynik: Cocoon Build System ------------------Building with classpath lib/ant_1_1.jar:lib/bsf.jar:lib/bsfengines.jar:lib/fop_0_13_0.jar:lib/ servlet_2_2.jar:lib/stylebook-1.0-b2.jar:lib/turbine-pool.jar:lib/ xalan_1_2_D02.jar:lib/xerces_1_2.jar:/usr/local/jdk1.2.2/lib/tools.jar: Starting Ant... Buildfile: build.xml init: ------------------- Cocoon 1.8 [1999-2000] --------prepare: [mkdir] Created dir: /home/adam/cocoon-1.8/build prepare-projectx: prepare-xt: prepare-ecma: prepare-ldap: prepare-src: [mkdir] Created dir: /home/adam/cocoon-1.8/build/src [mkdir] Created dir: /home/adam/cocoon-1.8/build/classes [copydir] Copying 126 files to /home/adam/cocoon-1.8/build/src
1
Chodzi o ten sam procesor FOP (procesor obiektów formatujących), który został przedstawiony w rozdziale 6., Przekształcanie XML-a, przy okazji opisywania konwersji XML-a na formaty nietekstowe.
2
Stylebook obsługuje tworzenie bardzo złożonych dokumentów (w tym w HTML-u); służy do generowania dokumentacji i strony WWW Cocoon. Projekt Stylebook ma być zintegrowany z Cocoon w wersji 2.0.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 208
Instalacja
209
compile: [javac] Compiling 108 source files to /home/adam/cocoon-1.8/build/classes [javac] Copying 18 support files to /home/adam/cocoon-1.8/build/classes package: [jar] Building jar: /home/adam/cocoon-1.8/build/cocoon.jar BUILD SUCCESSFUL Total time: 3 minutes 14 seconds
Uzyskany wynik może się nieco różnić numerami wersji lub liczbą plików, ale nie powinny wystąpić żadne błędy; jeśli wystąpiły, należy poprawić ścieżkę dostępu do klas zgodnie z komunikatami zgłaszanymi przez program i ponownie uruchomić skrypt budujący. Po zakończeniu procesu cała dystrybucja Cocoon powinna się znaleźć w jednym pliku cocoon.jar w podkatalogu build/. Należy również sprawdzić, czy posiadamy przykładowy plik właściwości dostarczany z projektem, cocoon.properties, w katalogu bin/. Jeśli go tam nie ma, być może znajduje się w podkatalogu build/classes/org/apache/cocoon/. Z pomocą Anta można także wygenerować dokumentację Javadoc i wykonać inne zadania związane z projektem. Wykonuje się je poprzez określenie celu (ang. target) polecenia budującego. Cele to słowa kluczowe dostarczane jako argumenty programu Ant; kompletna lista obsługiwanych celów dla oprogramowania Cocoon znajduje się w pliku build.xml. Celem do utworzenia dokumentacji jest docs, a dla Javadoc — javadocs. Na przykład, aby zbudować dokumentację, należy wykonać polecenie: Building with classpath lib/ant_1_1.jar:lib/bsf.jar:lib/bsfengines.jar:lib/fop_0_13_0.jar:lib/ servlet_2_2.jar:lib/stylebook-1.0-b2.jar:lib/turbine-pool.jar:lib/ xalan_1_2_D02.jar:lib/xerces_1_2.jar:/usr/local/jdk1.2.2/lib/tools.jar: Starting Ant... Buildfile: build.xml init: ------------------- Cocoon 1.8 [1999-2000] --------prepare-docs: [replace] Replacing ./xdocs/dtd/ --> dtd/ [replace] Replacing ./xdocs/dtd/ --> dtd/ docs: [mkdir] Created dir: /home/adam/cocoon-1.8/build/docs ...
W ten sposób pełna dokumentacja projektu znajdzie się w podkatalogu build/docs/. Po przygotowaniu projektu można przejść do konfigurowania mechanizmu serwletów pod kątem korzystania z niego.
Konfiguracja mechanizmu serwletów Trzeba poinformować mechanizm serwletów o oprogramowaniu Cocoon oraz wskazać, które żądania ma do tego oprogramowania przekazywać. Skonfigurujemy Cocoon do współpracy z mechanizmem serwletów Jakarta Tomcat3 — jest to implementacja referencyjna interfejsu Java 3
W przykładach wykorzystano wersję 3.1, ale Cocoon wymaga tylko wersji 2.x tego interfejsu serwletów. Opcje konfiguracyjne nie powinny się zmienić w innych wersjach; innymi słowy, instrukcje podane w tym rozdziale powinny „działać” w dowolnej wersji 3.x Tomcata.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 209
210
Rozdział 9. Struktury publikacji WWW
Servlet API 2.2. Czytelnik powinien potrafić uzyskać podobne wyniki także w innych mechanizmach serwletów. Struktura Cocoon działa na poziomie mechanizmu serwletów, a nie jako kolejny serwlet. Dlatego trzeba dodać Cocoon oraz współpracujące z nim mechanizmy do ścieżki dostępu do klas samego mechanizmu serwletów, a nie do określonej strefy serwletów czy kontekstu. Kopiujemy pliki jar Cocoon, Xerces, Xalan i FOP do podkatalogu lib/ głównego katalogu instalacyjnego Tomcat. Następnie biblioteki te dodajemy do ścieżki dostępu do klas mechanizmu serwletów; w tym celu w mechanizmie Tomcat edytujemy plik inicjalizacyjny. W systemie Windows jest to plik /bin/tomcat.bat, a w systemach Unix /bin/tomcat.sh. W plikach tych znajdziemy wiersze ustawiające ścieżkę dostępu do klas odczytywane przez Tomcat w czasie uruchamiania. Należy tam dodać ścieżki dostępu do klas Cocoon i programów towarzyszących — tak, by odczytywane były przed innymi wpisami w tym pliku4. W systemie Windows wpisy te wyglądają następująco: set CLASSPATH=. set CLASSPATH=%TOMCAT_HOME%\classes rem set set set set
Klasy i biblioteki Cocoon CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_2.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_2_D02.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_13_0.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar
a w systemach uniksowych: set CLASSPATH=. # Klasy i biblioteki Cocoon CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_2.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_1_2_D02.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_13_0.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar for i in ${TOMCAT_HOME}/lib/* ; do CLASSPATH=${CLASSPATH}:$i done
Ważne jest, aby klasy Cocoon poprzedzały pozostałe klasy Tomcat, a w szczególności xml.jar. Xerces, Xalan i Cocoon wykorzystują interfejs SAX 2 i DOM Level 2, zaś parser Sun Project X zawarty w xml.jar — jeszcze nie. Jeśli ta klasa i jej wersje SAX-a i DOM-a zostaną znalezione jako pierwsze, wystąpi błąd w strukturze publikacji Cocoon. Po dodaniu bibliotek trzeba jeszcze „poinformować” Cocoon o położeniu jego pliku właściwości (potem Czytelnik dowie się, do czego faktycznie służy ten plik). Należy skopiować plik cocoon.properties z katalogu Cocoon do /conf/. W tym samym katalogu jest umieszczony plik web.xml, opisujący właściwości i odwzorowania globalne serwletów w mechanizmie. Tutaj należy wskazać konfigurację struktury Cocoon. Wpisujemy tam następujące dane: 4
W najnowszych wersjach dla systemów uniksowych Tomcat sam odnajduje klasy skopiowane do lib/, dlatego podawanie ścieżek dostępu nie jest konieczne; trzeba jednak wtedy pamiętać, że pliki z tego katalogu będą odczytywane w kolejności zgodnej z kolejnością alfabetyczną nazw i w razie potrzeby odpowiednio zmienić te nazwy — przyp. tłum.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 210
Korzystanie ze struktury publikacji
211
org.apache.cocoon.Cocoon
org.apache.cocoon.Cocoon
properties
/usr/lib/jakarta-tomcat/conf/cocoon.properties
org.apache.cocoon.Cocoon
*.xml
Nie ma znaczenia, w którym miejscu pliku zostanie wstawiony powyższy fragment, o ile tylko nie naruszy to zagnieżdżenia elementów (ostateczny plik musi być poprawnym plikiem XML). Należy także zmienić na właściwą ścieżkę dostępu do pliku cocoon.properties, będącą wartością parametru properties. Parametr ten jest przekazywany przez mechanizm serwletów do serwleta Cocoon, co umożliwia poprawną jego konfigurację. Parametr servlet-mapping informuje mechanizm, że żądania kończące się rozszerzeniem .xml mają być przeadresowywane do struktury Cocoon. Po wprowadzeniu zmian należy wykonać restart serwera Tomcat i sprawdzić, czy w czasie uruchamiania nie występują żadne błędy. Jeśli wszystko zostało skonfigurowane poprawnie, można wyświetlić w przeglądarce stronę http:// :/Cocoon.xml. Jeśli nie wystąpiły żadne błędy, czytelnik powinien ujrzeć stronę podobną do tej na rysunku 9.1 (w zależności od rodzaju przeglądarki może się różnić formatowanie tabeli). Jeśli wystąpiły jakieś błędy, Czytelnik powinien ujrzeć komunikaty definiujące problem. Zazwyczaj kłopoty wynikają z nieobecności wymaganych klas w ścieżce dostępu do klas, w nieprawidłowym określeniu położenia pliku właściwości lub w niemożności jego odczytania przez serwlet. Ewentualne błędy naprawić i ponownie uruchomić mechanizm. Po uzyskaniu żądanego efektu Czytelnik jest gotów do wypróbowania działania struktury Cocoon i skonfigurowania jej w taki sposób, by obsługiwała różne typy żądań.
Korzystanie ze struktury publikacji Korzystanie z dobrej struktury publikacji w rodzaju Cocoon nie wymaga jakichś specjalnych instrukcji — nie jest to złożona aplikacja, której obsługi trzeba się najpierw nauczyć. Wszystko właściwie sprowadza się do wpisania prostego adresu URL do standardowej przeglądarki WWW. Generowanie dynamicznych stron HTML z XML-a, przeglądanie danych XML przekształconych na format PDF, a nawet aplikacje VRML generowane z XML-a — wszystko to uzyskać można poprzez wpisanie adresu URL pliku XML do przeglądarki; resztą zajmie się już Cocoon.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 211
212
Rozdział 9. Struktury publikacji WWW
Rysunek 9.1. Strona konfiguracji Cocoon
Przeglądanie plików XML przekonwertowanych na HTML Mając poprawnie działającą strukturę publikacji, możemy za jej pomocą wyświetlić posiadane pliki XML. Wraz z oprogramowaniem Cocoon dostarczane są przykładowe pliki XML i towarzyszące im arkusze stylów XSL (w podkatalogu samples/). My jednak mamy już własnoręcznie utworzone pliki XML i XSL, a więc spróbujmy przekształcić fragment spisu treści XML za pomocą arkusza stylów XSL utworzonego w rozdziale 6. Plik XML powinien nosić nazwę contents.xml (można go również pobrać ze strony WWW niniejszej książki). Należy skopiować go do katalogu głównego dokumentów mechanizmu serwletów. W przypadku domyślnej instalacji Tomcata jest to katalog /webapps/ROOT/. Dokument odwołuje się do arkusza stylów XSL/ JavaXML.html.xsl, a więc trzeba jeszcze w katalogu głównym utworzyć katalog XSL i skopiować tam arkusz utworzony w rozdziale 6. Należy sprawdzić, czy definicja DTD w dokumencie XML jest opatrzona komentarzami (pamiętajmy, sprawdzanie poprawności rzadko ma sens w zastosowaniu produkcyjnym), a także zamienić encję OReillyCopyright na HTML, tak jak to zostało opisane w rozdziale 6. Cocoon obsługuje sprawdzanie poprawności i encje zewnętrzne, ale na razie nie należy zaprzątać sobie głowy zbyt wieloma szczegółami. Teraz można już załadować plik, wpisując adres http://:/contents.xml. Jeśli Czytelnik wprowadził modyfikacje opisywane w rozdziale 6., przekształcony plik XML powinien wyglądać tak jak na rysunku 9.2.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 212
Korzystanie ze struktury publikacji
213
Rysunek 9.2. Plik XML z rozdziału 6. po przekształceniu
Pliki PDF utworzone z danych XML Do tej pory mówiliśmy niemal wyłącznie o przekształcaniu dokumentów XML na HTML; w pozostałych przypadkach zakładaliśmy, że dane będą przesyłane pomiędzy dwoma aplikacjami. Format był całkowicie umowny, jako że zarówno aplikacja wysyłająca, jak i otrzymująca przekształcały dane XML za pomocą określonej definicji DTD lub schematu. Jednakże struktura publikacji oferuje o wiele więcej możliwości. Nie tylko obsługiwane jest przekształcanie na liczne języki znaczników; dostępne są również biblioteki przekształcające XML na niektóre formaty nie wykorzystujące znaczników. Najbardziej popularną i stabilną biblioteką tej kategorii jest Formatting Objects Processor (FOP) grupy Apache XML, który pokrótce został już omówiony w rozdziale 6. Pozwala on na przekształcanie dokumentów XML na pliki PDF, których oglądanie umożliwia oprogramowanie Adobe Acrobat (http://www.adobe.com). Trudno przecenić możliwość przekształcania danych XML na dokumenty PDF; szczególnie w serwisach operujących na dokumentach, takich jak firmy drukarskie czy wydawnictwa, technologia ta może zrewolucjonizować operacje na danych. Poniższy przykład przedstawia dokument XML opisujący fragment oryginalnej, angielskiej wersji rozdziału 1. niniejszej książki (przykład 9.1).
Przykład 9.1. Wersja XML rozdziału 1.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 213
214
Rozdział 9. Struktury publikacji WWW
Java and XML Brett McLaughlin
XML. These three letters have brought shivers to almost every developer in the world today at some point in the last two years. While those shivers were often fear at another acronym to memorize, excitement at the promise of a new technology, or annoyance at another source of confusion for today's developer, they were shivers all the same. Surprisingly, almost every type of response was well merited with regard to XML. It is another acronym to memorize, and in fact brings with it a dizzying array of companions: XSL, XSLT, PI, DTD, XHTML, and more. It also brings with it a huge promise-what Java did for portability of code, XML claims to do for portability of data. Sun has even been touting the rather ambitious slogan "Java + XML = Portable Code + Portable Data" in recent months. And yes, XML does bring with it a significant amount of confusion. We will seek to unravel and demystify XML, without being so abstract and general as to be useless, and without diving in so deeply that this becomes just another droll specification to wade through. This is a book for you, the Java developer, who wants to understand the hype and use the tools that XML brings to the table. Today's web application now faces a wealth of problems that were not even considered ten years ago. Systems that are distributed across thousands of miles must perform quickly and flawlessly. Data from heterogeneous systems, databases, directory services, and applications must be transferred without a single decimal place being lost. Applications must be able to communicate not only with other business components, but other business systems altogether, often across companies as well as technologies. Clients are no longer limited to thick clients, but can be web browsers that support HTML, mobile phones that support Wireless Application Protocol (WAP), or handheld organizers with entirely different markup languages altogether. Data, and the transformation of that data, has become the crucial centerpiece of every application being developed today.
Czytelnik wie już, jak przekształcać taki dokument na dokument HTML. Ale przekształcenie całego rozdziału książki do tego formatu dałoby w wyniku ogromny dokument HTML, bardzo trudny w odczycie; potencjalny Czytelnik na pewno wolałby otrzymać dokument PDF. Z drugiej strony, jeśli generujemy statycznie PDF, to musimy zapewnić, że zmiany wprowadzone w rozdziale zostaną odzwierciedlone w nowym pliku PDF. Przechowywanie pliku w jednolitym formacie XML umożliwia prostą jego aktualizację (za pomocą dowolnego edytora XML), formatowanie do SGML-a w celu wydrukowania, przesyłanie do innych firm i aplikacji oraz dołączanie do innych książek lub kompendiów. Jeśli dodamy do tego prostotę użytkowania (wystarczy wpisać w przeglądarce adres URL), to otrzymujemy kompletny system publikacji.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 214
Korzystanie ze struktury publikacji
215
Nie będziemy szczegółowo omawiali obiektów formatujących i interfejsu FOP — definicja takich obiektów znajduje się w specyfikacji XSL pod adresem http://www.w3.org/TR/xsl/. W przykładzie 9.2 pokazano arkusz stylu XSL, który umożliwia przekształcenie danych XML na dokument PDF.
Przykład 9.2. Arkusz XSL konwertujący dane z przykładu 9.1 na dokument PDF
type="text/xslfo"
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 215
216
Rozdział 9. Struktury publikacji WWW
Po utworzeniu obu powyższych plików i zapisaniu rozdziału jako plik chapterOne.xml oraz arkusza jako JavaXML.fo.xsl w katalogu XSL/, można obejrzeć wyniki transformacji w przeglądarce. Przeglądarka musi być wyposażona we wtyczkę (ang. plug-in) Adobe Acrobat Reader, co umożliwi bezpośrednie obejrzenie pliku w przeglądarce (inaczej trzeba byłoby go zachować i przejrzeć przy użyciu innego programu). Wynikowy dokument PDF przedstawiony jest na rysunku 9.3.
Styl zależny od przeglądarki Oprócz jawnego określania typu transformacji (np. na format PDF), Cocoon umożliwia także przekształcanie w zależności od rodzaju żądania. Typowym przykładem takiego zachowania jest formatowanie zależne od programu klienta. W tradycyjnym środowisku WWW dokument XML może więc zostać przekształcony w różny sposób, zależnie od tego, czy klient korzysta np. z przeglądarki Internet Explorer czy Netscape. Biorąc pod uwagę ostatnie przepychanki firm Netscape i Microsoft odnośnie wersji HTML-a i DHTML-a, jest to na pewno bardzo przydatna funkcja. Cocoon fabrycznie obsługuje wiele popularnych typów przeglądarek. Poniżej ptrzedstawiony jest końcowy fragment pliku cocoon.properties. ########################################## # User Agents (Browsers) # ########################################## # # # # # #
NOTE: numbers indicate the search order. This is very important since some words may be found in more than one browser description. (MSIE is presented as "Mozilla/4.0 (Compatible; MSIE 4.01; ...") for example, the "explorer=MSIE" tag indicates that the XSL stylesheet associated to the media type "explorer" should be mapped to those browsers
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 216
Korzystanie ze struktury publikacji
217
Rysunek 9.3. Dokument PDF powstały z przykładów 9.1 i 9.2 # that have the string "MSIE" in their "user-Agent" HTTP header. browser.0 = explorer=MSIE browser.1 = pocketexplorer=MSPIE browser.2 = handweb=HandHTTP browser.3 = avantgo=AvantGo browser.4 = imode=DoCoMo browser.5 = opera=Opera browser.6 = lynx=Lynx browser.7 = java=Java browser.8 = wap=Nokia browser.9 = wap=UP browser.10 = wap=Wapalizer browser.11 = mozilla5=Mozilla/5 browser.12 = mozilla5=Netscape6/ browser.13 = netscape=Mozilla
Słowa kluczowe znajdujące się po pierwszych znakach równości (np. explorer, opera, lynx, java, netscape) określają agenty użytkowników i kody, jakie są przez nie wysyłane wraz z żądaniem. Spróbujmy wykorzystać te właściwości w przykładowym pliku arkusza XSL, stosowanym w przypadku, gdy klientem jest Internet Explorer. Kopiujemy nasz oryginalny arkusz stylów JavaXML.html.xsl do JavaXML.explorer-html.xsl i wprowadzamy zmiany przedstawione w przykładzie 9.3.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 217
218
Rozdział 9. Struktury publikacji WWW
Przykład 9.3. Arkusz stylu dla przeglądarki Internet Explorer
(wersja dla Explorera)
Table of Contents (wersja dla Explorera)
Natychmiast pobierz Netscape!
...
To oczywiście dość banalny przykład, ale dobrze obrazuje prostotę zmiany sposobu wyświetlania dokumentu i np. wysyłania innych danych do przeglądarki Internet Explorer 5.0, która szerzej obsługuje DHTML niż Netscape Navigator. Konieczne jest jeszcze poinformowanie dokumentu XML, że jeśli typ agenta użytkownika pasuje do zdefiniowanego we właściwościach typu explorer, to należy użyć innego arkusza XSL. Można to wykonać za pomocą instrukcji przetwarzania przedstawionej w przykładzie 9.4.
Przykład 9.4. Dokument XML odwołujący się do wielu arkuszy stylów w zależności od klienta
Jeśli dokument obejrzymy za pomocą Netscape Navigatora, uzyskamy takie wyniki jak poprzednio, a jeśli za pomocą Internet Explorera — uzyskamy dokument przekształcony za pomocą innego arkusza stylu (rysunek 9.4.).
WAP i WML Potężne możliwości takich dynamicznych arkuszy stylów wiążą się również z wykorzystaniem urządzeń bezprzewodowych. Oto znany już Czytelnikowi fragment pliku właściwości Cocoon:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 218
Rysunek 9.4. „Explorerowa” wersja wygenerowanej strony HTML Wyróżnione wpisy pozwalają wykryć agenta bezprzewodowego, np. telefon z dostępem do Internetu. Cocoon, tak jak wcześniej rozpoznawał przeglądarkę, teraz wysyła arkusz stylu odpowiedni dla urządzenia WAP. Do tej pory nie przywiązywaliśmy dużej wagi do poniższego fragmentu naszego pliku XML, określającego arkusz stylu używanego w przypadku takiego urządzenia:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 219
220
Rozdział 9. Struktury publikacji WWW
Teraz jednak omówimy go szczegółowo. W urządzeniach WAP wykorzystywany jest zazwyczaj język Wireless Markup Language (WML). Jest to odmiana HTML-a, różniąca się sposobem reprezentacji poszczególnych stron. Kiedy urządzenie bezprzewodowe wysyła żądanie pobrania pliku spod adresu URL, zwrócona treść musi być zawarta w znaczniku wml. W tym elemencie głównym definiuje się szereg kart (ang. cards) — każdą opisuje element WML card. Urządzenie pobiera wiele kart jednocześnie (określane są one mianem pliku kart (ang. deck of cards)), dzięki czemu nie musi ponownie odwoływać się do serwera w celu wyświetlenia kolejnych ekranów. W przykładzie 9.5 pokazano przykładową stronę XML wykorzystującą takie konstrukcje.
Przykład 9.5 Prosta strona WML
Menu główne Strona tytułowa Moja strona
Witam na mojej stronie domowej! Tak miło was tu gościć.
Hello World!
Na żądanie treści WML zwrócona zostałaby odpowiedź w postaci menu i dwóch ekranów zawartości, dostępnych poprzez odsyłacze z menu. Pełna specyfikacja WML 1.1 dostępna jest pod adresem http://updev.phone.com/dev/ts/; wcześniej trzeba się nieodpłatnie zarejestrować pod adresem http://updev.phone.com. Z tej witryny można również pobrać oprogramowanie UP.SDK, emulujące urządzenie bezprzewodowe. Wystarczy w nim wpisać adres http://:/contents.xml i obejrzeć wynik działania arkusza XSL dla urządzenia WAP. Ponieważ wyświetlacze telefonów są bardzo małe, będziemy chcieli wyświetlić jedynie podzbiór informacji zawartych w naszym spisie treści. W przykładzie 9.6 przedstawiono arkusz wyświetlający trzy karty w WML-u. Pierwsza to menu z odsyłaczami do dwóch pozostałych. Druga karta to wynik generacji spisu treści z dokumentu contents.xml. Trzecia to prosty ekran z prawami autorskimi. Arkusz stylów zachowujemy jako JavaXML.wml.xsl w podkatalogu XSL/ katalogu głównego serwera.
Przykład 9.6. Arkusz XSL umożliwiający wyświetlenie spisu treści w urządzeniu WAP
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 220
Korzystanie ze struktury publikacji
221
type="text/wml"
Spis Copyright
Copyright 2000, O'Reilly & Associates
Spis
Poza nowymi znacznikami WML, powyższy przykład nie powinien sprawiać Czytelnikowi żadnych trudności. Wprowadzono nową funkcję XSL, position() oraz nowy element xsl:number. W ten sposób w pętli xsl:for-each dodawana jest numeracja: 1: Wprowadzenie 2: Pisanie w XML-u ...
Dodano także instrukcje przetwarzania dla struktury Cocoon, w której cel określono jako cocoonformat. Przesyłane dane, type="text/wml", instruują Cocoon, że arkusz stylu ma być wyświetlony z nagłówkiem zawartości w postaci text/wml (a nie text/html lub text/plain, jak to się dzieje standardowo). I jeszcze jeden istotny dodatek w postaci atrybutu w elemencie głównym arkusza:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 221
222
Rozdział 9. Struktury publikacji WWW
Domyślnie każda deklaracja przestrzeni nazw XML inna niż przestrzeń XSL jest dodawana do elementu głównego danych wyjściowych transformacji. W naszym przykładzie element główny danych wyjściowych, wml, zostałby opatrzony następującą deklaracją przestrzeni nazw:
...
Spowodowałoby to błąd przeglądarki WAP, jako że xmlns:JavaXML nie jest dozwolonym atrybutem elementu wml. Przeglądarki tego typu nie są tak tolerancyjne jak przeglądarki HTML i reszta zawartości po prostu nie zostałaby wyświetlona. Jednakże my musimy zadeklarować przestrzeń nazw, aby arkusz XSL mógł dopasować szablon dla dokumentu wejściowego — a to właśnie wykonujemy poprzez przestrzeń JavaXML. Problem można rozwiązać poprzez dodanie atrybutu exclude-result-prefixes do elementu xsl:stylesheet. Przedrostek przestrzeni nazw określony w tym atrybucie nie będzie dodawany do danych wyjściowych — a tego właśnie sobie życzymy. Teraz dane wyjściowe będą „otoczone” znacznikami:
...
Taki zapis przeglądarka WAP rozumie doskonale. Jeśli pobraliśmy przeglądarkę UP.SDK, możemy wpisać w niej adres naszego spisu treści i obejrzeć wynik (do wyświetlenia stron WAP tłumacz wykorzystał emulator Deck-It firmy PyWeb, który można nieodpłatnie pobrać ze strony http://PyWeb.com; przeglądarka UP.SDK w czasie tłumaczenia nie była dostępna w wersji dla systemu Linux — przyp.tłum.). Na rysunku 9.5 widoczne jest menu główne wyświetlane wtedy, gdy pliku contents.xml zażąda poprzez Cocoon urządzenie WAP. Na rysunku 9.6 widzimy wygenerowany spis treści, dostępny po odwiedzeniu odsyłacza „Spis”, pokazanego na poprzednim rysunku. Więcej informacji o standardach WML i WAP można znaleźć na stronach http://www.phone.com i http://www.wapforum.org; oba te miejsca są obszernymi źródłami wiedzy o urządzeniach bezprzewodowych. Teraz już Czytelnik powinien dość dobrze orientować się, jak różne dane wyjściowe można uzyskać dzięki oprogramowaniu Cocoon. Niewielki wysiłek, jakiś dodatkowy arkusz stylu — i już ten sam dokument można serwować w wielu formatach rozmaitym klientom. To między innymi dlatego struktury publikacji są tak potężnym narzędziem. Bez XML-a i podobnego oprogramowania, dla każdego typu klienta trzeba byłoby tworzyć oddzielny serwis. Skoro jednak znamy już możliwości struktury Cocoon, przejdźmy do omówienia sposobu dynamicznego tworzenia i dostosowania danych wejściowych transformacji przy użyciu oprogramowania Cocoon.
XSP XSP to Extensible Server Pages, czyli rozszerzalne strony serwera. To chyba najważniejsza nowinka mająca swoje źródła w projekcie Cocoon. Z pewnością Czytelnik spotkał się już z terminem Java Server Pages (JSP). JSP to (w skrócie) znaczniki i zagnieżdżony kod Javy wstawiany do, skądinąd zupełnie zwyczajnej, strony HTML. Po otrzymaniu żądania takiej strony zawarty w niej kod
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 222
XSP
223
Rysunek 9.5. Główne menu WML
Rysunek 9.6. Spis treści WML
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 223
224
Rozdział 9. Struktury publikacji WWW
jest wykonywany, a wynik wstawiany bezpośrednio do danych wynikowych HTML5. Technologia ta zawładnęła Javą i ASP, upraszczając programowanie w Javie po stronie serwera i umożliwiając separację danych wyjściowych i procesów logicznych. Jednakże nie uniknięto pewnych istotnych problemów. Po pierwsze, JSP tak naprawdę nie oddziela zawartości i prezentacji. Problem ten już był sygnalizowany w książce — zmiana bannera, koloru fontu czy rozmiaru tekstu wymaga modyfikacji JSP (wraz z wpisanymi odwołaniami do Javy i JavaBean). Zawartość (czyste dane) miesza się z prezentacją, podobnie jak w statycznym HTML-u. Po drugie, nie ma możliwości przekonwertowania JSP na jakikolwiek inny format lub użycia go do komunikacji pomiędzy aplikacjami — JSP stworzono głównie do prezentacji danych wyjściowych. XSP stanowi remedium na oba te problemy. Po pierwsze, XSP to właściwie zwykły XML. W przykładzie 9.7 przedstawiona jest strona XSP.
Przykład 9.7. Przykładowa strona XSP
private static int numHits = 0; private synchronized int getNumHits() { return ++numHits; }
Licznik odwiedzin
Pobierano mnie getNumHits() razy.
Utrzymano zgodność ze wszystkimi konwencjami XML; chwilowo element xsp:logic odczytujemy jako „zakaz wstępu” dla parsera — element ten zostanie omówiony w dalszej części rozdziału. Poza tym cały dokument to zwykłe dane XML zawierające parę nowych elementów. Jest tu nawet odwołanie do arkusza stylu, który nie zawiera żadnych nowości (przykład 9.8).
Przykład 9.8. Arkusz stylu XSL dla przykładu 9.7
5
To oczywiście karygodne uproszczenie; JSP jest najpierw prekompilowane do postaci serwleta, a wyjście obsługuje PrintWriter. Więcej informacji o JSP znaleźć można w książce Java Servlet Programming Jasona Huntera (O'Reilly & Associates).
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 224
XSP
225 type="text/html"
Zatem XSP w prosty sposób likwiduje więc pierwszy problem związany z JSP — umożliwia oddzielenie zawartości od prezentacji. Dzięki takiej separacji programista może zająć się generacją treści (strona XSP może być statyczna, ale może być także generowana z serwletu lub innego kodu Javy), zaś autorzy XML i XSL zajmują się prezentacją i stylami poprzez modyfikację arkusza stylu. W podobny sposób rozwiązywana jest druga bolączka JSP — ponieważ przetwarzanie XSP wykonywane jest przed zastosowaniem arkuszy stylów, wynikowy dokument XML może zostać przetworzony na dowolny inny format. Strona XSP może więc być przekazywana pomiędzy aplikacjami równie łatwo, jak wykorzystywana w prezentacji.
Tworzenie strony XSP Teraz, kiedy Czytelnik poznał już XSP, spróbujmy stworzyć własną stronę w tym standardzie. Dalej jako przykład będziemy stosowali utworzony już wcześniej dokument XML. Zamiast po prostu wyświetlać nasz dokument, załóżmy, że autor chciałby pokazywać go redaktorowi w miarę pisania. Oprócz tekstu książki redaktor powinien widzieć komentarze autora; komentarze te nie są jednak przeznaczone dla oczu zwykłego czytelnika — na przykład dotyczą formatowania i stylu. Najpierw dodajmy taki komentarz do stworzonego wcześniej pliku chapterOne.xml.
Chapter 1: Introduction XML. These three letters have brought shivers to almost every developer in the world today at some point in the last two years. While those shivers were often fear at another acronym to memorize, excitement at the promise of a new technology, or annoyance at another source of confusion for today's developer, they were shivers all the same. Surprisingly, almost every type of response was well merited with regard to XML. It is another acronym to memorize, and in fact brings with it a dizzying array of companions: XSL, XSLT, PI, DTD, XHTML, and more. It also brings with it a huge promise-what Java did for portability of code, XML claims to do for portability of data. Sun has even been touting the rather ambitious slogan "Java + XML = Portable Code + Portable Data" in recent months. And yes, XML does bring with it a significant amount of confusion. We will seek to unravel and demystify XML, without being so abstract and general as to be useless, and without diving in so deeply that this becomes just
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 225
226
Rozdział 9. Struktury publikacji WWW another droll specification to wade through. This is a book for you, the Java developer, who wants to understand the hype and use the tools that XML brings to the table. Czy ten akapit jest poprawnie sformatowany, czy rozdzielamy go na dwa oddzielne akapity? Mike, poinformuj mnie o swojej decyzji w tym zakresie. Today's web application now faces a wealth of problems that were not even considered ten years ago. Systems that are distributed across thousands of miles must perform quickly and flawlessly. Data from heterogeneous systems, databases, directory services, and applications must be transferred without a single decimal place being lost. Applications must be able to communicate not only with other business components, but other business systems altogether, often across companies as well as technologies. Clients are no longer limited to thick clients, but can be web browsers that support HTML, mobile phones that support Wireless Application Protocol (WAP), or handheld organizers with entirely different markup languages altogether. Data, and the transformation of that data, has become the crucial centerpiece of every application being developed today.
Po wstawieniu do dokumentu takiego komentarza dodajmy odpowiedni wpis w arkuszu stylów, JavaXML.fo.xsl.
Po wprowadzeniu powyższego wpisu komentarze będą nieco mniejsze niż reszta tekstu, niebieskie i pisane kursywą. Teraz nasz dokument XML należy przekształcić na stronę XSP (jak w przykładzie 9.9) — dodając wymagane instrukcje przetwarzania dla struktury Cocoon i otaczając elementy nowym elementem głównym, xsp:page.
Przykład 9.9. Wersja XSP przykładu 9.1
Java i XML Brett McLaughlin
getTitle(, "") - getDraftDate()
Kiedy w dokumencie odwołującym się do takiej biblioteki znaczników użyjemy elementu JavaXML:draftTitle, do wyniku metody getTitle() zostanie dołączony łącznik (-), a następnie wartość zwracana przez getDate(). Element JavaXML:draftTitle również wymaga zadeklarowania dwóch atrybutów: numeru rozdziału i tytułu w postaci tekstowej. Procesor XSP otrzymuje informację, że wywołujemy zdefiniowaną metodę, poprzez ujęcie wywołania tej metody w znaczniki . Aby zaznaczyć, że drugi argument (tytuł rozdziału) jest zmienną String, ujmujemy go w cudzysłowy. Ponieważ tytuł rozdziału powinien być traktowany jako int, zostawiamy go bez cudzysłowów. Po stworzeniu arkusza logiki XSP (można go również pobrać ze strony WWW niniejszej książki) trzeba go udostępnić strukturze Cocoon. Można to zrobić na dwa sposoby. Pierwszy z nich polega na określeniu położenia pliku (w postaci URI), aby mógł zostać odnaleziony przez mechanizm serwletów (a więc również przez Cocoon). Na przykład, aby dodać w ten sposób arkusz XSP do zasobów Cocoon, należy wpisać następujący wiersz do pliku cocoon.properties. Wersja uniksowa:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 232
Takie rozwiązanie wystarcza na potrzeby testowania, ale arkusze logiczne są wtedy silnie „związane” z mechanizmem serwletów, a dodawanie nowych jest kłopotliwe (konieczne jest wpisywanie kolejnych wierszy w pliku właściwości)6. Alternatywna metoda ładowania arkuszy logiki polega na określeniu zasobu w ścieżce dostępu do klas mechanizmu serwletów. Arkusze logiki dodawane są do pliku jar, a plik ten — do ścieżki dostępu do klas. W ten sposób w pliku powstaje centralna baza własnych arkuszy logiki XSP użytkownika. W podkatalogu XSL/ wykonujemy następujące polecenie, które spowoduje utworzenie pliku jar, zawierającego nasz arkusz logiki: jar cvf logicsheets.jar JavaXML.xsp.xsl
6
Co więcej, zdarzają się kłopoty (wersje 1.7.x) z załadowaniem arkusza wpisanego w postaci file://...; problemu tego można uniknąć stosując zapis w postaci resource://
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 233
234
Rozdział 9. Struktury publikacji WWW
Utworzone w ten sposób archiwum logicsheets.jar przenosimy do katalogu /lib/ — tam, gdzie znajdują się pozostałe biblioteki Cocoon. Teraz musimy dodać tę bibliotekę do ścieżki dostępu do klas serwera Tomcat; edytujemy plik tomcat.sh lub tomcat.bat zlokalizowany w katalogu /bin/. W systemie Unix plik ten ma wyglądać następująco: set CLASSPATH=. # Klasy i biblioteki Cocoon CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_2.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_1_2_D02.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_13_0.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/logicsheets.jar for i in ${TOMCAT_HOME}/lib/* ; do CLASSPATH=${CLASSPATH}:$i done CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/tools.jar
Natomiast w Windows: set CLASSPATH=. set CLASSPATH=%TOMCAT_HOME%\classes rem set set set set set
Klasy i biblioteki Cocoon CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_2.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_2_D02.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_13_0.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\logicsheets.jar
Teraz informujemy Cocoon, gdzie ma, szukać przestrzeni nazw JavaXML na stronach XSP. Edytujemy plik cocoon.properties, który wcześniej umieściliśmy w katalogu /conf. Odnajdujemy część mówiącą o zasobach XSP i dodajemy odwołanie do nowego arkusza logiki: processor.xsp.logicsheet.context.java = resource://org/apache/cocoon/processor/xsp/library/java/context.xsl processor.xsp.logicsheet.cookie.java = resource://org/apache/cocoon/processor/xsp/library/java/cookie.xsl processor.xsp.logicsheet.global.java = resource://org/apache/cocoon/processor/xsp/library/java/global.xsl processor.xsp.logicsheet.request.java = resource://org/apache/cocoon/processor/xsp/library/java/request.xsl processor.xsp.logicsheet.response.java = resource://org/apache/cocoon/processor/xsp/library/java/response.xsl processor.xsp.logicsheet.session.java = resource://org/apache/cocoon/processor/xsp/library/java/session.xsl processor.xsp.logicsheet.util.java = resource://org/apache/cocoon/processor/xsp/library/java/util.xsl processor.xsp.logicsheet.sql.java = resource://org/apache/cocoon/processor/xsp/library/sql/sql.xsl processor.xsp.logicsheet.fp.java = resource://org/apache/cocoon/processor/xsp/library/fp/fp.xsl processor.xsp.logicsheet.JavaXML.java
= resource://JavaXML.xsp.xsl
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 234
Cocoon 2.0 i dalej
235
Ponieważ nasz arkusz logiki nie jest zagnieżdżony w podkatalogach pliku logicsheets.jar, jako ścieżkę dostępu wystarczy określić tutaj nazwę pliku. Po tych wszystkich operacjach restartujemy mechanizm serwletów — plik cocoon.properties zostanie ponownie załadowany, a arkusz logiki udostępniony do użycia. Ponieważ żądania obsługuje mechanizm Cocoon, każda strona XSP, na której zadeklarowano użycie JavaXML, będzie miała dostęp do arkusza logiki określonego jako biblioteka JavaXML. Tak więc do naszej strony XSP musimy dodać deklarację przestrzeni nazw JavaXML:
...
Skoro biblioteka znaczników jest już udostępniona, dodajemy element JavaXML:draftTitle do naszego dokumentu XML, chapterOne.xml:
Zakodowany „na stałe” tytuł rozdziału zamieniamy na element zdefiniowany w naszej bibliotece znaczników. W ten sposób zostanie wygenerowany tytuł i numer rozdziału oraz data wersji roboczej. Teraz można już zobaczyć efekt naszej pracy, wpisując adres dokumentu do przeglądarki WWW. To, oczywiście, proste przykłady zastosowania standardu XSP. Ale już tutaj widać potężne możliwości XSP — tytuł może przybrać inną postać po ukończeniu rozdziału bez konieczności modyfikacji zawartości czy warstwy prezentacyjnej strony, a jedynie arkusza XSP. Logika aplikacji jest odseparowana od zawartości i prezentacji. Do tego wszystkiego można jeszcze dołączyć logikę biznesową za pomocą komponentów Javy uruchamianych po stronie serwera, np. Enterprise JavaBeans. Zastosowanie standardu XSP to rozwiązanie bardziej elastyczne niż JSP połączony z HTML-em — poszczególne komponenty nie są związane ze sobą tak ściśle. Standard XSP ma stanowić kluczowy element powstającej wersji 2.0 Cocoon.
Cocoon 2.0 i dalej Kolejna wersja Cocoon, o numerze 2.0 stanowi ogromny krok naprzód w porównaniu z wersjami wcześniejszymi. Cocoon 1.x, w którym przede wszystkim przetwarza się XML za pośrednictwem interfejsu XSL, jest wciąż w szerokim stopniu ograniczony. Po pierwsze, oprogramowanie to na razie nie przyczynia się do znacznego zmniejszenia kosztów utrzymania dużych serwisów. Dokument XML może zostać przekształcony w różne formy prezentacji, ale i tak na serwerze musi istnieć spora liczba dokumentów. Konieczne jest więc korzystanie z długich identyfikatorów URI
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 235
236
Rozdział 9. Struktury publikacji WWW
(np. /content/publishing/books/javaxml/contents.xml), wielu odwzorowań wirtualnych ścieżek (/javaxml odwzorowane na /content/publishing/books/javaxml/) albo z połączenia obu. Co więcej, wciąż nie jest łatwo odseparować prezentację od zawartości i logiki aplikacji. W projekcie Cocoon 2 skoncentrowano się na rozdzieleniu tych różnych warstw — ma to przyczynić się do zmniejszenia kosztów zarządzania witryną; całość ma skupiać się wokół standardu XSP. Plan witryny (o którym za chwilę) spowoduje, że użytkownik nie będzie widział różnicy pomiędzy stronami XSP, XML i statycznymi HTML. Wprowadzone zostaną również istotne zmiany w kompilacji i zarządzaniu pamięcią; Cocoon 2 będzie o tyle bardziej zaawansowany od Cocoon 1.x, o ile Cocoon 1.x był bardziej zaawansowany od zwykłego serwera WWW.
Odwzorowania w mechanizmie serwletów Istotna zmiana w Cocoon 2 polega na tym, że nie będzie już trzeba stosować prostego odwzorowywania dokumentów XML. Odwzorowanie takie spisuje się nieźle w modelu 1.x, ale zarządzanie dokumentami innymi niż XML zrzucane jest na barki webmastera — osoby niekoniecznie związanej z administratorem odpowiedzialnym za dokumenty XML. Cocoon 2 ma umożliwić zarządzanie całą witryną. Główny serwlet Cocoon (org.apache.cocoon.servlet.CocoonServlet w modelu 2.0) jest zazwyczaj odwzorowywany na identyfikator URI, np. /Cocoon. Możliwe jest także odwzorowanie na katalog główny całego serwera (czyli /) i tym samym sterowanie całą witryną. Adres URL przyjmuje postać wynikającą z takiego odwzorowania, np. http://mojHost.com/ Cocoon/mojaStrona.xml lub http://mojHost.com/Cocoon/mojaDynamicznaStrona.xsp. Przy takim odwzorowaniu nawet statyczne dokumenty HTML można grupować z dokumentami XML. Możliwe jest więc centralne zarządzanie wszystkimi plikami umieszczonymi na serwerze. Jeśli dokumenty HTML i XML pojawią się w jednym katalogu, nie będzie kłopotu z ich oddzielną obsługą ani z tworzeniem identyfikatorów URI. Cocoon 2 będzie obsługiwał HTML oraz dane wszelkiego innego typu; po odwzorowaniu głównego katalogu serwera na Cocoon cała struktura publikacji staje się niewidoczna dla klienta.
Plan witryny Kolejną ważną nowością w Cocoon 2 jest plan witryny (ang. sitemap). W strukturze Cocoon plan witryny stanowi centralne miejsce administracji serwisem. Na podstawie tego planu Cocoon decyduje o tym, jak przetwarzać otrzymany identyfikator URI. Na przykład, kiedy otrzymuje żądanie w rodzaju http://mojaWitrynaCocoon.com/Cocoon/javaxml/chapterOne.html, serwlet Cocoon bada żądanie i określa, że faktycznie żądano /javaxml/chapterOne.html. Załóżmy jednak, że chapterOne.html nie ma być odwzorowywany na statyczny plik HTML, ale na wynik transformacji dokumentu XML (jak w naszych wcześniejszych przykładach). Tu właśnie przydaje się plan witryny. Spójrzmy na plan witryny w przykładzie 9.12.
Przykład 9.12. Przykładowy plan witryny w Cocoon 2.0
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 236
Cocoon 2.0 i dalej
237
Definicja DTD planu witryny jest już na ukończeniu, ale w trakcie testowania beta mogą zostać wprowadzone zmiany. Powyższy przykład należy traktować jako zarys możliwości Cocoon 2, a nie faktycznie działający kod.
Cocoon dopasowuje identyfikator URI /javaxml/chapterOne.html do instrukcji planu witryny /javaxml/*.html. Następnie określa, że jest to faktyczny plik i że odnaleziony on zostanie po użyciu odwzorowania /docs/javaxml/*.xml, tłumaczonego na /docs/javaxml/chapterOne.xml (nazwa pliku, którą chcemy przetworzyć). Następnie stosowany jest filtr XSLT; wykorzystywany arkusz stylu, JavaXML.html.xsl, jest także określany w planie witryny. Wynik transformacji wyświetlany jest użytkownikowi. Co więcej, plik XML może być plikiem XSP, przetwarzanym przed konwersją na XML. Według tego samego schematu generowany jest plik PDF z żądania http://mojaWitrynaCocoon.com/Cocoon/javaxml/chapterOne.pdf — wystarczy tylko dodać parę wierszy do planu witryny (patrz wyżej). Oznacza to również, że instrukcje przetwarzania w indywidualnych dokumentach można usunąć, co stanowi istotną zmianę w stosunku do Cocoon 1.x. Stosowanie arkuszy stylów i przetwarzania odbywa się na podstawie położenia katalogu. Wystarczy stworzyć dokument XML i umieścić go w katalogu /docs/javaxml (zgodnie z powyższym przykładem), a dokument ten będzie można pobrać jako HTML lub PDF. Zmiana arkuszy stylów wykorzystywanych w dokumentach jest również banalna — w poprzedniej wersji był to proces kłopotliwy. Zamiast zmieniać każdy dokument XML, wystarczy zmienić jeden wiersz w planie witryny. Plan witryny jest wciąż w trakcie rozwoju i prawdopodobnie będzie jeszcze zmieniany. Aby przyłączyć się do prac, należy zapisać się na listy adresowe [email protected] i [email protected]. Pod adresem http://xml.apache.org można znaleźć szczegółowe informacje o całym projekcie Cocoon.
Producenty i procesory Ostatnim istotnym ulepszeniem w strukturze Cocoon 2 jest wprowadzenie wstępnie kompilowanych i skoncentrowanych wokół zdarzeń producentów i procesorów (ang. producers, processors). W strukturze Cocoon producent obsługuje transformację żądania URI na strumień danych XML. Procesor pobiera następnie strumień wejściowy (obecnie w postaci dokumentu XML w drzewie DOM) i wysyła dane wyjściowe możliwe do odczytania przez klienta. Nie omówiliśmy tych komponentów w modelu Cocoon 1.x, ponieważ w modelu 2.0 ulegną one bardzo dużym zmianom; te, które używane są obecnie, staną się prawdopodobnie bezużyteczne i trzeba będzie je przepisać. W Cocoon 2 zrezygnowano z używania w tych strukturach modelu DOM na rzecz bardziej skoncentrowanego wokół zdarzeń SAX-a (a nawet JDOM-a!), „opakowanego” strukturą DOM. Do tej pory producent musiał generować dokument XML w pamięci i odpowiadająca mu struktura DOM mogła rozrosnąć się do wielkich rozmiarów. To powodowało wyczerpanie zasobów systemowych,
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 237
238
Rozdział 9. Struktury publikacji WWW
szczególnie w tak skomplikowanych zadaniach jak złożone transformacje czy obsługa obiektów formatujących (generowanie PDF). Dlatego DOM będzie teraz tylko „interfejsem” do zdarzeń SAX, a producenty i procesory zyskają na wydajności. Producenty i procesory będą kompilowane z innych formatów. Na przykład arkusze XSL można skompilować do procesorów, a strony XSP — do producentów. To jeszcze bardziej zwiększa wydajność i zdejmuje ciężar przetwarzania z klienta. Tutaj i wszędzie indziej wykorzystywany będzie podział na poszczególne komponenty — nowy Cocoon będzie dzięki temu bardziej elastyczny i łatwy w rozbudowie. Najnowsze wiadomości dotyczące nowego projektu można znaleźć na stronie WWW Cocoon.
Co dalej? W następnym rozdziale zostanie omówiona technologia umożliwiająca wykorzystanie XML-a jako formatu danych w modelu żądań i odpowiedzi XML-RPC. Rozproszony system wywoływania zdalnych procedur XML-RPC umożliwia wykonywanie zadań na serwerze (lub serwerach) znajdujących się w innej części sieci. Do niedawna nie cieszył się on zbytnią popularnością, co spowodowane było przede wszystkim napływem technologii opartych na RMI i związanych z Javą (szczególnie EJB). Jednakże przy wykorzystaniu XML-a jako formatu danych, XML-RPC umożliwia rozwiązanie wielu problemów.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 238
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 wywoływania zdalnych procedur. Jeśli Czytelnik nie jest doświadczonym programistą, bądź też do tej pory 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 niepozorne literki przed skrótem RPC zrewolucjonizowały coś, co wydawało się być dinozaurem w świecie programistycznym. Czytelnik nauczy się również korzystać z XML-RPC z poziomu programó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 tymi językami i sposobami programowania odsunięto na bok technologię RPC — nowe, obiektowe 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ównać ją z podobnymi rozwiązaniami Javy, a szczególnie z technologią RMI (Remote Method Invocation). 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 programistom — 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 istotne. 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 aplika-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 239
240
Rozdział 10. XML-RPC
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 Magnusson) lub Java Distributed Computing Jima Farleya (obie książki wydawnictwa O'Reilly & Associates).
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 zagłę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 procedury 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ą sieciową. 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ż korzystanie z różnych protokołów (np. Internet Inter-ORB Protocol) — dzięki temu można uruchomić 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 zasoby. 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 charakteryzuje 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 bardziej 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 zostanie dowiązany do nazwy odpowiadającej takiemu usługodawcy, dopóty nie będzie dostępny z poziomu 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 opisującego metody dostępne do wywołania oraz (jeśli korzystamy z EJB) szeregu innych interfejsó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 samodzielnych (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ć
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 240
RPC kontra RMI
241
jako sposób korzystania z „usług” komputerów zdalnych, z kolei RMI umożliwia korzystanie z „serweró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 rozdział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 komunikację dosłownie dowolnych aplikacji — protokołem transportowym może być bowiem HTTP. Ponieważ 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 wynik 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 dodawać 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. Wyobraź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 korzyści płynące z zastosowania technologii RPC. Do niedawna zachodziła odwrotnie proporcjonalna 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 reprezentacje danych nie zostały ustandaryzowane i wymagały całkowicie nowych implementacji w poszczegó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ą reprezentację 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 — korzystają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 standardowy; to uprościło implementację bibliotek RPC. Pozostała więc już tylko do uruchomienia transmisja 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 rozwiązanie zdalnego wywoływania procedur.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 241
242
Rozdział 10. XML-RPC
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 trzeba już się uczyć o RMI, aby korzystać z usług rozproszonych (przynajmniej na początku). Niniejszy 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 technologii 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 procedur, 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ć prawdziwy program wykorzystujący tę technologię. Zgodnie z tradycją, zaczniemy od prostego programu „Witaj świecie!”. W naszym serwerze XML-RPC zostanie zarejestrowana procedura obsługi. Procedura ta pobiera parametr String Javy (nazwę użytkownika) i zwraca łańcuch zawierający „Witaj” oraz pobraną nazwę, np. „Witaj Brett”. Następnie procedura ta zostanie udostępniona przez serwer klientom XML-RPC. Na koniec stworzymy prostego klienta łączącego się z serwerem 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. Jednakże, jeśli nie mamy pod ręką kilku komputerów, przykłady możemy przećwiczyć lokalnie. Proces będzie przebiegał wtedy szybciej niż w rzeczywistym zastosowaniu, ale i tak będzie można zaobserwować, 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 odpowiednich 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ą specyfikację 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 programistycznym IDE. Następnie kompilujemy klasy; jeden z przykładów serwletów wymaga obecnoś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 zamierza „bawić się” przykładem z serwletem, to nie musi go kompilować — nie jest on wymagany do wykonania przykładów w tym rozdziale.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 242
Powiedział „Witaj!”
243
Klasy XML-RPC są spakowane w pliku zip, xmlrpc-java.zip. Z tego archiwum trzeba wydobyć cały kod źródłowy znajdujący się w katalogu xmlrpc-java/src/. Nie dołączono dystrybucji w postaci jar, więc wymagana jest ręczna kompilacja klas. Po skompilowaniu 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, serwer XmlRpcServer, XmlRpcHandler (precyzyjne sterowanie kodowaniem i przetwarzaniem XML-a) oraz szereg klas pomocniczych. Klasy zgłaszają wyjątek XmlRpcException; natomiast 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 specyficznego sterownika SAX. Wymagane do pracy całego mechanizmu, a nie zawarte w dystrybucji, 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ługują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ść przyję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.XmlRpcServer. Trzeba jedynie napisać klasę z jedną lub dwoma metodami, które zamierzamy zarejestrować w serwerze. Niespodzianką jest fakt, że tworzenie procedury obsługi nie wymaga budowania podklas ani innych 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 sygnaturach 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).
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 243
244
Rozdział 10. XML-RPC
Tabela 10.1. Typy Javy obsługiwane przez XML-RPC Typ danych XML-RPC
Typ danych Javy
int boolean string double dateTime.iso8601 struct array base64
int boolean java.lang.String double java.util.Date java.util.Hashtable1 java.util.Vector1 byte[]
Przykład 10.1. Klasa procedury obsługi z metodą uruchamianą zdalnie /** * HelloHandler to prosta procedura obsługi * rejestrowana w serwerze XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloHandler { /** *
* * @param name String nazwa użytkownika. * @return String - 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 XMLRPC, 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łoby 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 pozwolić na określenie portu, na którym serwer zostanie uruchomiony, a następnie spowodować, aby 1
Oczywiście, typy struct i array muszą zawierać tylko inne dozwolone typy XML-RPC.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 244
Powiedział „Witaj!”
245
serwer nasłuchiwał żądań XML-RPC dopóty, dopóki nie zostanie wyłączony. Następnie zarejestrujemy w tym serwerze stworzoną wcześniej klasę i określimy inne parametry specyficzne dla naszej aplikacji. Stwórzmy teraz szkielet tej klasy (przykład 10.2); konieczne jest zaimportowanie klasy WebServer oraz pobranie portu z wiersza poleceń.
Przykład 10.2. Szkielet serwera XML-RPC „Witaj!” import helma.xmlrpc.WebServer; /** * HelloServer to prosty serwer XML-RPC, * który udostępni klasę HelloHandler * wywołaniom XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloServer { /** *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ 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 przetworzenia 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 Parser2. 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 ClassNotFoundException, a więc trzeba go przechwycić w celu określenia, czy klasa sterownika została odnaleziona. Wprowadźmy teraz opisywane zmiany:
2
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.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 245
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 WebServer, 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 wywołania, to serwer XML-RPC jest już gotowy do pracy. Dodamy linijkę tworzącą i uruchamiają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 porcie, 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; ... *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ public static void main(String[] args) { if (args.length < 1) { System.out.println( "Użycie: java HelloServer [port]"); System.exit(-1); }
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 246
Powiedział „Witaj!”
247
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 komunikat 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 metod. W RMI zdalny interfejs posiada sygnatury metod odpowiadające wszystkim metodom zdalnym. Jeśli metoda implementowana jest na klasie serwera, ale do zdalnego interfejsu nie dodano 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 postaci „nazwaklasy.nazwametody”). Serwer RPC „widzi”, że metoda znajduje się w klasie „nazwaklasy” i nosi nazwę „nazwametody”. Próbuje więc odnaleźć pasującą klasę i metodę pobierającą parametr takiego typu, jaki jest w żądaniu RPC. Po znalezieniu określona metoda zostaje wywoł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 jawnie definiowana w serwerze XML-RPC, a jedynie w żądaniu otrzymanym od klienta. W serwerze rejestrowany jest jedynie egzemplarz klasy. Do klasy tej można dodawać metody, a ich udostępnienie wymaga jedynie ponownego uruchomienia serwera — a nie zmian w kodzie. O ile tylko potrafimy 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, szkieletó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 obsł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 poniższym przykładzie stworzenie egzemplarza nowej klasy jest dobrym rozwiązaniem. Zarejestrujemy 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:
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 247
248
Rozdział 10. XML-RPC import java.io.IOException; import helma.xmlrpc.WebServer; import helma.xmlrpc.XmlRpc; /** * HelloServer to prosty serwer XML-RPC, * który udostępni klasę HelloHandler * wywołaniom XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloServer { /** *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ 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ć podobny do tego z przykładu 10.33.
3
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.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 248
Powiedział „Witaj!”
249
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 wywołującego zdalnie naszą metodę sayHello(). Korzystamy w tym celu z klasy helma.xmlrpc.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, sprawdzając argument przekazywany do metody sayHello() na serwerze oraz wykonując podstawową obsługę wyjątków. Tworzymy plik źródłowy Javy HelloClient.java — taki jak w przykładzie 10.4.
Przykład 10.4. Klient XML-RPC import helma.xmlrpc.XmlRpc; import helma.xmlrpc.XmlRpcClient; /** * HelloClient to prosty klient XML-RPC * wysyłający żądanie do HelloServer. * * @author Brett McLaughlin * @version 1.0 */ public class HelloClient { /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
*/ 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");
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 249
Tak jak cały kod w tym rozdziale, powyższy przykład nie powinien sprawiać Czytelnikowi żadnych 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; /** * HelloClient to prosty klient XML-RPC * wysyłający żądanie do HelloServer. * * @author Brett McLaughlin * @version 1.0 */ public class HelloClient { /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
*/ 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
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 250
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 komponentami naszego rozwiązania. Oczywiście, w kodzie źródłowym należy wpisać numer portu, na jakim zamierzamy uruchomić serwer. Rzecz jasna, nie jest to rewelacyjny sposób uzyskiwania komunikacji pomiędzy klientem a serwerem; zmiana portu, na którym nasłuchuje serwer, wymaga 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 XmlRpcClient 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ą klasy, 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ć wektora Vector Javy i powinny zawierać wszystkie obiekty parametrów wymagane przez określoną metodę. W naszej prostej metodzie sayHello() jest to String zawierający nazwę użytkownika, którą podajemy w wierszu poleceń. Kiedy już klient XML-RPC zakoduje żądanie, wysyła je do serwera. Serwer odnajduje klasę pasującą do podanego identyfikatora i szuka odpowiedniej metody. Jeśli zostanie ona znaleziona, przyjmowane 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, klientowi zgłasza się wyjątek XmlRpcException. To gwarantuje, że klient nie będzie próbował wywoł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
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 251
252
Rozdział 10. XML-RPC
ostatni wyjątek zgłaszany jest w przypadku nieprawidłowej komunikacji pomiędzy klientem a serwerem. Następnie dodajemy klasę Vector i tworzymy jej egzemplarz, podając mu jeden parametr 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; ... /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
To już wszystko! Przykład kompilujemy i uruchamiamy zgodnie z poniższym opisem.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 252
Po co się męczyć? Niech serwer nas wyręczy!
253
Mów do mnie... Należy sprawdzić, czy w ścieżce dostępu do klas znajdują się klasy XML-RPC i klasy opisywanych 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 oddzielny 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 uruchomienie 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ę odpowiedź 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ównież zobaczyć, jak wyglądają procedury obsługi XML-RPC. Następnie stworzymy klienta (podobnego 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 zastosowanie technologii XML-RPC w środowisku produkcyjnym — zbudujemy bardziej użyteczną procedurę obsługi i bardziej praktyczny serwer. Kod taki, choć może wciąż jeszcze nieprzydatny 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 bardziej złożonych zadań na serwer — uproszczony klient ma tylko żądać wywołania procedury i ko-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 253
254
Rozdział 10. XML-RPC
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żytkowane 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, zawierającego nazwę i czas zdarzenia w formacie java.util.Date. Nie jest to może kompletna implementacja rozkładu zadań, ale dobrze demonstruje działanie serwera, wykonuje pewne zadania „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; ciekawe jest natomiast to, jak zachowamy te zadania w klasie Scheduler. Serwer XML-RPC stworzy 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 rozkładem zajęć będą komunikowały się inne klasy lub wręcz inne serwery XML-RPC. Jeśli listę zdarzeń będziemy składować jako zmienną przynależną do klasy, nie będzie możliwe współużytkowanie 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 zdarzenia 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 zdarzeń, 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; /** * Scheduler 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) */
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 254
* Tutaj inicjalizujemy miejsce składowania zdarzeń. *
*/ public Scheduler() { events = new Vector(); eventDetails = new Hashtable(); } /** *
* Tutaj dodajemy żądane zdarzenie. *
* * @param eventName String nazwa zdarzenia do dodania. * @param eventTime Date data zdarzenia. * @return boolean - 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; } /** *
* Tutaj usuwamy żądane zdarzenie. *
* * @param eventName String nazwa zdarzenia do usunięcia. * @return boolean - 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żonych aplikacjach można ją wykorzystać do informowania o problemach związanych z dodawaniem 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 XMLRPC, inny serwer XML-RPC, inną aplikację albo samodzielną implementację tego samego rozkładu zadań. Ponieważ zwracane dane to pojedynczy Object, można zwrócić Vector sformatowa-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 255
256
Rozdział 10. XML-RPC
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 datą 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.SimpleDateFormat, 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 wymaganą instrukcję importującą oraz kod zwracający zdarzenia: import java.text.SimpleDateFormat; import java.util.Date; import java.util.Hashtable; import java.util.Vector; ... /** *
* Tutaj zwracamy bieżącą listę zdarzeń. *
* * @return Vector - 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 528
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 329
330
Rozdział 13. Firma-firma 156592391X 36.95
279
0553293362 5.59
Może nie jest to kompletny dokument XML, ale sam ten fragment jest sformatowany poprawnie i mógłby zostać wstawiony do dokumentu XML zawierającego już nagłówek i element główny. I tak właśnie zrobimy, kiedy za chwilę zajmiemy się mechanizmem wyświetlającym listę książek. Reszta skryptu zwraca odpowiednie dane HTML w zależności od tego, czy dodawanie książki się powiodło, czy nie. W pierwszym przypadku dostawca zobaczy proste potwierdzenie, takie jak na rysunku 13.2.
Rysunek 13.2. Komunikat potwierdzający dodanie książki Teraz można już wykorzystać ten fragment dokumentu do udzielania odpowiednich informacji stronom żądającym.
Wyświetlanie listy dostępnych książek Do wyświetlenia listy nowych książek znów posłuży Perl. Załóżmy, że jakaś inna porcja aplikacji biblioteki okresowo odczytuje dane XML i aktualizuje katalog. Ten składnik aplikacji byłby odpowiedzialny również za usuwanie wpisów w naszym pliku, tak aby książki już wprowadzone do katalogu nie były klasyfikowane jako „nowe”. Zadanie dla naszego drugiego skryptu w Perlu jest więc następujące: odczytać fragment kodu XML i dodać dane do dokumentu wyświetlanego na ekranie. Jak już było wspomniane, konieczne jest także dodanie deklaracji XML i elementu
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 330
Biblioteka Publiczna NaNiby
331
głównego, „otaczającego” dostarczoną zawartość. Nowy skrypt (przykład 13.3) odczytuje plik utworzony przez addBook.pl i wyświetla odpowiednią zawartość po otrzymaniu żądania przez HTTP.
Przykład 13.3. Skrypt Perl CGI wyświetlający dokument XML z nowymi książkami #!/usr/local/bin/perl $baseDir = "/usr/local/projects/javaxml/foobar/books/"; $filename = "books.txt"; $bookFile = $baseDir . $filename; # Najpierw otwieramy plik. open(FILE, $bookFile) || die "Could not open $bookFile.\n"; # Przekazujemy przeglądarce informacje o rodzaju przesyłanych danych. print "Content-type: text/plain\n\n"; # Wyświetlamy nagłówek i element główny XML. print "\n"; print "\n"; # Wyświetlamy książki. while () { print "$_"; } # Zamykamy element główny. print "\n"; close(FILE);
Powyższy skrypt, zachowany jako supplyBooks.pl, przyjmie żądanie, odczyta plik stworzony przez addBook.pl i wyświetli dane XML po żądaniu HTTP. Przykładowy wynik (z dodanymi książkami) można obejrzeć na rysunku 13.3.
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 331
332
Rozdział 13. Firma-firma
Rysunek 13.3. Wynik działania skryptu supplyBooks.pl Jak widać, prostą aplikację w Perlu łatwo można zamienić w składnik, który potrafi dostarczyć użyteczne informacje klientom — takim jak księgarnia KsiegarniaTechniczna.com. Zrobiliśmy to bez instalacji nowego oprogramowania, bez zmiany architektury systemu lub aplikacji i bez pisania choćby jednej linijki kodu w Javie!
KsiegarniaTechniczna.com Biblioteka Publiczna NaNiby umożliwia już uzyskanie dostępu do danych o nowych książkach w formacie XML. Teraz KsiegarniaTechniczna.com jest już bliska osiągnięcia zamierzonego celu — dostarczyć aktualne informacje swoim klientom. W firmie tej już dawno wykorzystuje się Javę do budowania aplikacji, co bardzo uprości proces uzyskiwania dostępu i korzystania z danych XML oferowanych przez bibliotekę — Java świetnie obsługuje XML, o czym zresztą przekonuje cała niniejsza książka. Najpierw chcemy umożliwić wyświetlanie w firmie KsiegarniaTechniczna.com danych dotyczących nowych książek, a następnie udostępnić te informacje klientom tej firmy.
Filtrowanie danych XML W firmie KsiegarniaTechniczna.com mają być wyświetlane wyłącznie dane o książkach technicznych. Jak zapewne Czytelnik pamięta, Biblioteka Publiczna NaNiby pozwalała na wprowadzanie książek o zróżnicowanej tematyce; KsiegarniaTechniczna.com akceptuje wyłącznie książki o problematyce informatycznej. Na szczęście, odpowiednia informacja jest zawarta w atrybucie subject elementu book. Najpierw należy więc odfiltrować wszystkie książki, których tematem nie jest „Computers”. Kiedy już uzyskamy w ten sposób książki o tematyce informatycznej, musimy je sformatować do postaci strony HTML, którą pokażemy klientom odwiedzającym witrynę firmy KsiegarniaTechniczna.com.
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 332
KsiegarniaTechniczna.com
333
Tutaj nie będziemy korzystali ze strony statycznej, ponieważ strona z nowymi pozycjami musi zostać wygenerowana przy każdej próbie jej pobrania. Oczywiście, tym razem do obsługi takich żądań wykorzystamy serwlet. Do konwersji danych XML z biblioteki na postać HTML świetnie nadawałby się Apache Cocoon, ale pracownicy firmy KsiegarniaTechniczna.com muszą całe zadanie wykonać jak najszybciej i na razie nie chcą wprowadzać tak dużych zmian w istniejącym systemie. Wolą wykorzystać parsery i procesory XML, a dopiero w drugiej fazie wdrażania zainstalować Cocoon. A więc trzeba obsłużyć konwersję XML-HTML, a oprócz tego jeszcze filtrowanie i dodawanie elementów prezentacyjnych (np. logo firmy czy pasek menu). Ale jeśli Czytelnik przywoła całą wiedzę o XML-u i XSL-u, to być może przypomni sobie, że nawet bez Cocoona można przekształcać dokumenty XML na format HTML-a — za pomocą arkuszy XSL. W trakcie transformacji można również odfiltrować książki o tematyce innej niż komputerowa. Pamiętając o tym wszystkim, możemy już stworzyć arkusz stylu, który zostanie zastosowany do dokumentu przesłanego z Biblioteki Publicznej NaNiby. Początkowy fragment takiego arkusza, obsługujący generację dokumentu HTML, został zamieszczony w przykładzie 13.4.
Przykład 13.4. Arkusz stylu dla danych otrzymywanych z Biblioteki Publicznej NaNiby
Księgarnia Techniczna - Twój sklep z literaturą komputerową
Twoje źródło książek o tematyce komputerowej i technicznej.
Nowe pozycje
Strona główna
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 333
334
Rozdział 13. Firma-firma Obecne pozycje
Nowe pozycje
Kontakt
Witamy w firmie KsiegarniaTechniczna.com, oferującej książki o tematyce komputerowej i technicznej. Po lewej stronie zamieszczono najnowsze tytuły. Aby nabyć którąś z tych wspaniałych książek, wystarczy kliknąć odsyłacz "Kup książkę!" co spowoduje jej przeniesienie do koszyka. Miłych zakupów!
Można także przejrzeć nasze wcześniejsze tytuły, zajrzeć do informacji o sklepie, albo skontaktować się z nami. Wszystkie niezbędne odsyłacze znajdują się po lewej stronie. Dziękujemy za zakupy u nas!
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 335
336
Rozdział 13. Firma-firma
Autor: Wydawca: Stron: Cena:
/servlets/BuyBookServlet?isbn= =
Kup książkę!
Szablon dopasowuje element book, a następnie tworzy tabelę z nagłówkiem w jednym wierszu i zawartością w drugim. Cała tabela umieszczona jest wewnątrz innej tabeli, dzięki czemu w przeglądarce ta pierwsza tabela sprawia wrażenie otoczonej czarną ramką. Tytuł wstawiany jest do nagłówka tabeli, a informacje o książce (autor, wydawca, liczba stron i cena) do zawartości tabeli. Jest tam również odsyłacz do serwleta Javy BuyBookServlet, za pomocą którego można w prosty sposób zakupić daną książkę. Jako argument wywołania serwleta podawany jest numer ISBN tej książki. W arkuszu XSL trzeba sprawdzić, czy wiersz opisujący użycie BuyBookServlet oraz wiersz z elementem xsl:value-of tworzą tak naprawdę jedną linijkę. W przeciwnym razie do wynikowego adresu URL mogłyby „przedostać się” spacje i znaki nowego wiersza i serwlet otrzymałby niepoprawne dane. W przedstawionym arkuszu stylów, w wyniku łamania tekstu w druku, te informacje zostały rozbite na dwa wiersze.
Teraz trzeba jeszcze zagwarantować, że nowy szablon zostanie zastosowany i że zostaną do niego przekazane wyłącznie książki o tematyce „Computers”. Wartość atrybutu subject poznamy poprzez odwołanie się do niego za pomocą symbolu @; filtrowanie nastąpi poprzez atrybut select elementu xsl:apply-templates:
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 336
KsiegarniaTechniczna.com
337
Odwołujemy się do wartości atrybutu i porównujemy go z łańcuchem znaków (w apostrofach — to dlatego, że całe wyrażenie XPath znajduje się w cudzysłowach). Ponieważ „obrabiamy” atrybut elementu zagnieżdżonego, odwołujemy się do niego przez nazwę i otaczamy wyrażenie związane z jego atrybutem nawiasami kwadratowymi. W ten sposób gwarantujemy, że szablon zostanie zastosowany tylko na książkach o tematyce „Computers”. Po stworzeniu arkusza stylów można go zachować jako computerBooks.xsl i odwoływać się do niego poprzez serwlet Javy, który zostanie przedstawiony poniżej.
XSLT poprzez serwlet Arkusz stylu jest gotowy. Potrzebny jest jeszcze kod w Javie, który spowoduje zastosowanie tego arkusza na danych z biblioteki. Dostęp do danych odbędzie się za pośrednictwem klasy Javy URL, poprzez którą wyślemy żądanie HTTP do systemu biblioteki. Po tym wszystkim pozostanie już tylko wykonać faktyczną transformację z poziomu programu. W przykładzie 13.5 przedstawiono kod serwleta ładującego dane XML z biblioteki i ukazującego, w którym miejscu znajdzie się kod przekształcający (wykonujący transformację).
Przykład 13.5. Serwlet Javy przekształcający informacje o książkach na postać kodu HTML package com.techbooks; import import import import import import import
public class ListBooksServlet extends HttpServlet { /** Host, z którego pobierzemy listę książek */ private static final String hostname = "newInstance.com"; /** Port powyższego hosta */ private static final int portNumber = 80; /** Plik (ścieżka URI) zawierający listę książek */ private static final String file = "/cgi/supplyBooks.pl"; /** Arkusz stylu, według którego przekształcimy XML */ private static final String stylesheet = "/home/client/java/newinstance/www/javaxml/techbooks/XSL/computerBooks .xsl"; public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); // Łączymy się w celu pobrania listy książek URL getBooksURL = new URL("http", hostname, portNumber, file); InputStream in = getBooksURL.openStream(); // Przekształcamy dane XML za pomocą arkusza stylu } }
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 337
338
Rozdział 13. Firma-firma
Ten prosty serwlet wysyła żądanie HTTP do biblioteki i otrzymuje odpowiedź w języku XML w postaci strumienia InputStream1. Strumień zostanie następnie wykorzystany jako parametr procesora XSLT oraz jako baza do przekształcenia przez arkusz stylu zdefiniowany w serwlecie jako statyczny. Obecnie nie istnieje interfejs API Javy, który określałby, jak programowo wykonać transformację XSLT; jednakże każdy producent procesora powinien udostępniać klasy umożliwiające uruchomienie przekształcania z kodu Javy. Tutaj ponownie zostanie wykorzystany procesor Apache Xalan; informacji o sposobie wywoływania metod transformacji w innym parserze należy zasięgnąć u producenta. W przypadku procesora Apache Xalan do opisanego celu służy klasa XSLTProcessor z pakietu org.apache.xalan.xslt. Jako parametry przyjmuje ona klasę XSLTInputSource reprezentującą plik XML do przetwarzania, klasę XSLTInputSource reprezentującą arkusz stylu oraz XSLTResultTarget reprezentującą wynik transformacji. Wszystkie te klasy pomocnicze znajdują się również w pakiecie org.apache.xalan.xslt. Klasy te tworzy się w wygodny sposób, przekazując InputStream (do XSLTInputSource) lub OutputStream (do XSLTResultTarget). Nasz dokument ma postać obiektu InputStream, arkusz stylu „zapakujemy” w FileInputStream, zaś interfejs serwleta pozwala nam w prosty sposób uzyskać dostęp do obiektu ServletOutputStream poprzez metodę getOutputStream(), wywołaną na obiekcie HttpServletResponse. Na koniec trzeba jeszcze omówić sposób uzyskania egzemplarza XSLTProcessor. Ponieważ do przetwarzania mogą być wykorzystane różne mechanizmy wewnętrzne, egzemplarza tej klasy nie tworzy się bezpośrednio, ale poprzez klasę XSLTProcessorFactory, również znajdującą się w pakiecie org.apache.xalan.xslt. Czytelnik wie już, jak korzystać z klas typu „factory”, a więc pozostało tylko zaimportować wymagane klasy i dodać do serwleta wywołania metod przetwarzających: package com.techbooks; import import import import import import import
// Import komponentów procesora Xalan XSLT import org.apache.xalan.xslt.XSLTInputSource; import org.apache.xalan.xslt.XSLTProcessor; import org.apache.xalan.xslt.XSLTProcessorFactory; import org.apache.xalan.xslt.XSLTResultTarget; public class ListBooksServlet extends HttpServlet { /** Host, z którego pobierzemy listę książek */ private static final String hostname = "newInstance.com"; /** Port powyższego hosta */ private static final int portNumber = 80; /** Plik (ścieżka URI) zawierający listę książek */ private static final String file = "/cgi/supplyBooks.pl";
1
Więcej informacji o klasie URL i operacjach wejścia-wyjścia Javy można znaleźć w książce Java I/O Eliotte Rusty Harolda (O'Reilly & Associates).
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 338
KsiegarniaTechniczna.com
339
/** Arkusz stylu, według którego przekształcimy XML */ private static final String stylesheet = "/home/client/java/newinstance/www/javaxml/techbooks/XSL/computerBooks .xsl"; public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); // Łączymy się w celu pobrania listy książek URL getBooksURL = new URL("http", hostname, portNumber, file); InputStream in = getBooksURL.openStream(); try { XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); // Przekształcamy XML za pomocą arkusza stylu processor.process(new XSLTInputSource(in), new XSLTInputSource(new FileInputStream(stylesheet)), new XSLTResultTarget(res.getOutputStream())); } catch (Exception e) { PrintWriter out = res.getWriter(); out.println("Błąd: " + e.getMessage()); out.close(); } } }
Procesor wysyła dane wyjściowe do ServletOutputStream, a więc nie trzeba nawet dodawać żadnych dodatkowych danych wynikowych, oczywiście poza przypadkami błędów! Teraz można zachować serwlet jako ListBooksServlet.java i po kompilacji będzie on dostępny poprzez mechanizm serwletów i przeglądarkę. Jeśli Czytelnik stara się wykonywać opisywane przykłady, to przed uruchomieniem serwleta będzie musiał przedsięwziąć pewne kroki. Po pierwsze, musi ustanowić połączenie z Internetem albo dokonać zmian w przykładach, tak aby mogły być uruchamiane lokalnie; serwlet obsługujący KsiegarniaTechniczna.com musi mieć dostęp do danych biblioteki. Po drugie, warto wpisać kilka książek o różnej tematyce do systemu biblioteki poprzez interfejs HTML. W ten sposób Czytelnik będzie mógł zobaczyć, co dokładnie robią aplikacje i jak ze sobą współpracują. Kiedy już dane znajdują się w bibliotece, a do samej biblioteki mamy dostęp, można uruchomić ListBooksServlet.
Uruchomiony serwlet wysyła żądanie pobrania danych z Biblioteki Publicznej NaNiby. Dane te (lista nowych książek) są następnie przekształcane i wyświetlane na ekranie jako HTML. Odpowiedź z serwleta powinna wyglądać podobnie do tej pokazanej na rysunku 13.4 — widzimy spis nowych książek i aktualne informacje o nich (dzięki zmianom w bibliotece!); mamy także odsyłacze pozwalające na natychmiastowy zakup wybranej książki. Klienci firmy KsiegarniaTechniczna.com w prosty sposób przeglądają nowe tytuły online; pozostaje jeszcze „wypchnąć” te informacje do klientów tak, aby nie musieli nawet wpisywać adresu URL. Rozwiązanie tego problemu znajduje się w dalszej części rozdziału.
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 339
340
Rozdział 13. Firma-firma
Pobieranie a „wypychanie” informacji Do tej pory aplikacje tworzyliśmy z założeniem, że klienci sami będą pobierali dane i zawartość. Innymi słowy, klient musiał wpisać adres URL w przeglądarce (jak przy liście nowych książek w witrynie KsiegarniaTechniczna.com), lub też aplikacja taka jak opisywany wyżej serwlet musiała wysłać żądanie HTTP pobrania danych XML (jak w bibliotece). Oczywiście, nie ma tutaj żadnego problemu — ale takie rozwiązanie nie zawsze jest najlepsze z marketingowego punktu widzenia. Klienci pobierający dane muszą pamiętać, gdzie kupować książki, a często po jednej wizycie nie powracają w dane miejsce przez dni, tygodnie i miesiące. Sporadyczne zakupy na pewno nie przynoszą takich dochodów jak transakcje nawet mniejsze, ale za to częstsze. Dlatego KsiegarniaTechniczna.com chce mieć możliwość wypychania (ang. push) danych do klientów. Klient dowiaduje się o nowych pozycjach wydawniczych lub promocjach bez konieczności wykonywania jakichkolwiek działań. Wynikiem są częstsze zakupy — klient nie musi sam pamiętać o odwiedzaniu strony. Ale w Internecie nie jest łatwo uzyskać takie wypychanie danych — sieć globalna to nie klient uproszczony, nie da się tak po prostu wyświetlić okienka „pop-up” z nowymi informacjami. Popularność zyskują natomiast „strony startowe” w rodzaju My Netscape firmy Netscape czy My Yahoo firmy Yahoo. W rozmowach z Netscape przedstawiciele firmy KsiegarniaTechniczna.com usłyszeli o technologii RSS — może to jest właśnie sposób uzyskania zaplanowanego celu?
Rysunek 13.4. Strona HTML powstała w wyniku pobrania listy książek
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 340
Pobieranie a „wypychanie” informacji
341
Rich Site Summary Rich Site Summary (RSS) to szczególna odmiana języka XML. Posiada własną definicję DTD i definiuje pojęcie kanału. Kanał to sposób reprezentacji danych na określony temat. Zawiera tytuł i opis kanału, grafikę (logo) oraz szereg pozycji (ang. items) kanału. Każda pozycja to coś, co wiąże się z kanałem, ewentualnie produkt lub usługa. Ponieważ dozwolone elementy pozycji są raczej ogólne (tytuł, opis, odsyłacz), pozycją kanału może być niemal wszystko. Kanał RSS nie ma dostarczać kompletnej zawartości serwisu, lecz raczej „streszczenie” informacji o firmie lub usłudze, wyświetlane w postaci „portalowej”, ewentualnie jako pasek informacji na innej stronie WWW. Tak naprawdę poszczególne „widżety” w serwisie Netscape Netcenter to właśnie kanały RSS. Netscape udostępnia możliwość tworzenia nowych kanałów RSS, rejestrowanych w Netcenter. Netscape posiada również wbudowany system wyświetlania kanałów w formacie HTML, które wtedy świetnie spisują się jako strony startowe Netcenter. Czytelnik może w tej chwili niepokoić się, czy RSS nie jest czasem dla firmy Netscape czymś takim jak parser Microsoft XML dla firmy Microsoft — czymś zupełnie nieużytecznym w połączeniu z innymi narzędziami czy produktami innych firm. Rozwiązanie to na początku faktycznie było opracowywane pod kątem Netcenter; jednak dzięki strukturze języka XML technologia RSS może zostać wykorzystana przez dowolną aplikację potrafiącą „czytać” pliki DTD. Coraz więcej portali i aplikacji korzysta z RSS — przykładem może być projekt Apache Jetspeed (http://java.apache.org/jetspeed), mający na celu stworzenie systemu Enterprise Information Portal w „stylu” open source. Jetspeed wykorzystuje dokładnie ten sam format RSS co Netscape, ale w zupełnie inny sposób. Jest to możliwe dzięki temu, że RSS ma bardzo spójną gramatykę. Wielu użytkowników korzysta ze stron „startowych” lub „domowych” — miejsc najczęściej odwiedzanych w sieci. KsiegarniaTechniczna.com postanowiła stworzyć kanał RSS przedstawiający listę najnowszych książek i umożliwiający zainteresowanym natychmiastowy zakup określonego tytułu. To właśnie nazwać można „wypychaniem” danych — produkty takie jak Netcenter automatycznie aktualizują zawartość kanału RSS tak często, jak życzy sobie tego klient.
Tworzenie dokumentu RSS XML Po pierwsze, konieczne jest utworzenie pliku RSS. A to jest prostsze, niż można byłoby przypuszczać — poza wpisaniem odwołania do właściwej DTD i trzymania się określonych w tej definicji zasad, nie ma tutaj nic skomplikowanego. W przykładzie 13.6 pokazano propozycję pliku RSS dla naszej księgarni.
Przykład 13.6. Przykładowy dokument RSS opisujący kanał firmy KsiegarniaTechniczna.com
KsiegarniaTechniczna.com Nowe Tytuły http://www.newInstance.com/javaxml/techbooks/
Twoje źródło książek i materiałów o tematyce komputerowej.
en-us
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 341
Doskonałe wprowadzenie do serwletów Javy i różnych mechanizmow komunikacyjnych.
Element główny musi nazywać się rss; musi zostać także zdefiniowany atrybut version. Oprócz tego, wartość tego atrybutu musi odpowiadać wersji DTD, do której się odwołujemy. W elemencie głównym musi wystąpić dokładnie jeden element channel. Wewnątrz niego znajdują się natomiast elementy opisujące dany kanał (title, link, description i language). Z kanałem można również skojarzyć opcjonalny plik graficzny (oraz informacje o tym pliku). Następnie może wystąpić do 15 elementów item, z których każdy opisuje jedną pozycję związaną z kanałem. Każdy item posiada elementy title, link i description (ich znaczenia nie trzeba chyba wyjaśniać). Tak jak w poprzednich przykładach, również tutaj w elementach link i url nie powinny znaleźć się białe znaki — całość powinna mieć postać jednego wiersza. W druku nie było to możliwe, dlatego wiersz został rozdzielony.
Można także dodać opcjonalne pole tekstowe i przycisk do wysyłania informacji, czego w tym przykładzie nie pokazano. Szczegółowy opis elementów i atrybutów znajduje się na stronie http:// my.netscape.com/publish/help/mnn20/quickstart.html. Tworzenie plików RSS programowo nie jest trudne; procedura podobna jest do tej, jaka została wykorzystana przy generowaniu pliku HTML na potrzeby księgarni. Połowa pliku RSS (informacje o kanale oraz o grafice) to treść statyczna; tylko elementy item są budowane dynamicznie. Ale kiedy już programista uruchamia edytor vi i zasiada do tworzenia kolejnego arkusza XSL, na biurku znajduje kartkę z kolejnym wymogiem: komputer, na którym znajdzie się kanał RSS, to inny serwer niż ten używany w ostatnim przykładzie; dostępne są na nim tylko bardzo stare wersje bibliotek Apache Xalan. Ponieważ na maszynie tej działają również pewne aplikacje wysokiej dyspozycyjności (np. system naliczania należności), KsiegarniaTechniczna.com nie chce aktualizować tych bibliotek, chyba że przy precyzyjnej kontroli efektów zmian (taki proces zajmuje tydzień). Na samym serwerze są jednak dostępne nowsze wersje bibliotek Xerces (ponieważ w sys-
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 342
Pobieranie a „wypychanie” informacji
343
temie naliczania konieczne jest przetwarzanie XML-a), a więc dostępne są również interfejsy API Javy do obsługi XML-a.2 Oczywiście, dałoby się tutaj wykorzystać interfejsy SAX i DOM, ale po raz kolejny najlepszą metodą wydaje się JDOM. To za jego pomocą (przykład 13.7) dokonamy konwersji pliku XML z Biblioteki Publicznej NaNiby na format kanału RSS.
Przykład 13.7. Serwlet Javy konwertujący listę nowych książek na dokument kanału RSS package com.techbooks; import import import import import import import import import
// JDOM import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.Builder; import org.jdom.input.SAXBuilder; public class GetRSSChannelServlet extends HttpServlet { /** Z tego hosta pobieramy listę książek */ private static final String hostname = "newInstance.com"; /** Numer portu powyższego hosta */ private static final int portNumber = 80; /** Ścieżka URI prowadząca do dokumentu */ private static final String file = "/cgi/supplyBooks.pl"; public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/plain"); PrintWriter out = res.getWriter(); // Łączymy się i pobieramy listę książek URL getBooksURL = new URL("http", hostname, portNumber, file); InputStream in = getBooksURL.openStream(); try { // Żądamy implementacji SAX i korzystamy z domyślnego parsera. Builder builder = new SAXBuilder(); // Tworzymy dokument. Document doc = builder.build(in); // Wyświetlamy XML. out.println(generateRSSContent(doc));
2
Tak, to nieco wydumany przykład, pewnie mało prawdopodobny. Ale pozwala Czytelnikowi poznać jeszcze jedną alternatywę co do sposobu programowego tworzenia XML-a. Jednak tak naprawdę wszystkie przykłady w tej książce (łącznie z tymi głupiutkimi) oparte są na prawdziwych doświadczeniach związanych z prowadzeniem konsultingu w rzeczywistych firmach!
C:\WINDOWS\Pulpit\Szymon\Java i XML\13-08.doc — strona 343