12-08, Programowanie, ! Java, Java i XML


12

Tworzenie danych XML w języku Java

W tym rozdziale zostanie omówione jeszcze jedno zagadnienie związane z operowaniem na da­nych XML — tworzenie i modyfikowanie danych. Do tej pory w naszych aplikacjach korzys­ta­liśmy z istniejących dokumentów XML i stałych danych. Nie wprowadzaliśmy zmian do pier­wot­nego dokumentu. I to często wystarczy; jednak w coraz większej liczbie nowoczesnych rozwiązań dokumenty XML tworzy się dynamicznie, w pamięci (tak działa np. interfejs XSP, omówiony w rozdziale 9.). Inne aplikacje tego typu to edytory XML i środowiska programistyczne, a także menedżery konfiguracji, które zostaną przedstawione w niniejszym rozdziale.

W poprzednim rozdziale został utworzony plik konfiguracyjny XML zawierający informacje doty­czą­ce konfiguracji klas XML-RPC. Założyliśmy, że zmiany konfiguracji będą wprowadzane „ręcz­nie” przez administratora. Potem plik taki będzie sprawdzany pod kątem poprawności (o ile administrator nie będzie leniwy), po czym nastąpi ponowne uruchomienie serwera i klientów XML-RPC. Takie rozwiązanie jest obarczone dużym ryzykiem wystąpienia błędów. Po pierwsze, zakładamy, że przy wprowadzaniu danych nie popełniono żadnego błędu. Po drugie, że admi­ni­stra­tor jest na tyle zdyscyplinowany, że dokona sprawdzenia poprawności zmienionego doku­men­tu XML. Nawet jeśli wszystko do tej pory się sprawdzi przy każdej zmianie konfiguracji (co jest mało prawdopodobne w rzeczywistych zastosowaniach), konfiguracja staje się skomplikowana, jeśli klient i serwer znajdują się na różnych komputerach. Jeśli serwer XML-RPC ma również postać rozproszoną, to plik konfiguracyjny może znajdować się w innym miejscu; być może nawet w czterech czy pięciu oddzielnych kopiach. Zmiany muszą być wprowadzane równolegle we wszystkich kopiach. Dobrym rozwiązaniem byłby tu serwlet lub aplikacja Javy modyfikująca plik konfiguracyjny i automatycznie aktualizująca go w różnych miejscach. I tutaj właśnie wymaga się od Javy „umiejętności” modyfikacji danych XML.

W tym rozdziale Czytelnik dowie się, jak zmodyfikować i zachować zmieniony dokument XML. Wynikowe dane powinny zostać zapisane na dysku twardym jako inny plik XML, jako strumień da­nych przekazywany do innego składnika aplikacji lub jako dane XML przekształcane do postaci HTML. Wszystko to jest możliwe do uzyskania z poziomu interfejsu Javy do obsługi danych XML.

Ładowanie danych

Podobnie jak w poprzednich rozdziałach, aby Czytelnik mógł dobrze poznać opisywaną tech­no­logię i interfejsy, powinien wypróbować je na przykładzie. W związku z tym po raz kolejny roz­budujemy nasz pakiet klas i narzędzi XML-RPC. W poprzednim rozdziale dokonaliśmy migracji danych konfiguracyjnych naszej aplikacji do postaci danych XML. Mamy już klasę, która potrafi od­­czytać konfigurację w tej postaci i wykorzystać uzyskane informacje do uruchomienia klientów i serwera XML-RPC poprzez interfejsy JDOM; Czytelnik poznał także sposoby uzyskania tego samego rezultatu za pomocą SAX-a i DOM-a. Teraz napiszemy proste narzędzie do aktualizacji i modyfikacji konfiguracji oraz zapisywania tych zmian w pliku konfiguracyjnym.

W tej części omówimy dwa składniki tego procesu: klasę narzędziową com.oreil­ly.xml. XmlRpcConfiguration, która obecnie zajmuje się ładowaniem danych, oraz serwlet Javy z in­terfejsem do edycji danych. Najpierw w naszej klasie narzędziowej stworzymy mutatory, które uzupełnią akcesory stworzone w rozdziale 11. Dzięki nim stworzony potem serlwet, a także inne aplikacje, będą potrafiły modyfikować dane w klasie narzędziowej. Po stworzeniu tych podstaw do modyfikacji danych konfiguracyjnych, trzeba będzie utworzyć serwlet wyświetlający te infor­macje oraz umożliwiający ich modyfikację poprzez formularz HTML.

Punkt wyjścia do modyfikacji danych

Ponieważ w naszej klasie narzędziowej XmlRpcConfiguration zaszyty jest proces odczy­ty­wania i zapisywania dokumentu XML w systemie plików, dodamy teraz do niej mutatory, a na­stęp­nie procedury faktycznie zapisujące zmiany do pliku. W ten sposób powstanie warstwa abstrakcyjna pozwalająca na budowanie aplikacji w sposób równoległy — kiedy już wstawimy odpowiednie me­tody, jeden programista lub też grupa może pracować nad interfejsem w postaci serwletu z wy­ko­rzystaniem akcesorów i mutatorów, zaś inna grupa może opracować metodę wewnątrz klasy, za­chowującą nową konfigurację. W przykładzie 12.1 przedstawiono klasę Xml­RpcConfiguration, w której zaimplementowano mutatory; jest tu także szkielet metody saveConfiguration, która ostatecznie będzie służyła do zapisu danych konfiguracyjnych po podaniu nazwy pliku lub strumienia OutputStream.

Przykład 12.1. Klasa narzędziowa zawierająca mutatory

package com.oreilly.xml;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.IOException;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.List;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.JDOMException;

import org.jdom.Namespace;

import org.jdom.input.Builder;

import org.jdom.input.DOMBuilder;

/**

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

* ładująca dane konfiguracyjne dla serwerów i klientów XML-RPC.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class XmlRpcConfiguration {

/** Z tego strumienia odczytujemy dane konfiguracyjne XML */

private InputStream in;

/** Port, na którym działa serwer */

private int portNumber;

/** Nazwa hosta, na którym uruchomiono serwer */

private String hostname;

/** Klasa sterownika SAX do załadowania */

private String driverClass;

/** Procedury obsługi do zarejestrowania w serwerze XML-RPC */

private Hashtable handlers;

/** Dowiązanie JDOM Document do pliku XML */

private Document doc;

/**

* <p>

* Tutaj określamy plik, z którego odczytamy

* informacje o konfiguracji.

* </p>

*

* @param filename <code>String</code> nazwa pliku

* zawierającego konfigurację XML.

*/

public XmlRpcConfiguration(String filename)

throws IOException {

this(new FileInputStream(filename));

}

/**

* <p>

* Tutaj określamy plik, z którego odczytamy

* informacje o konfiguracji.

* </p>

*

* @param in <code>InputStream</code> z którego

* odczytamy informacje o konfiguracji.

*/

public XmlRpcConfiguration(InputStream in)

throws IOException {

this.in = in;

portNumber = 0;

hostname = "";

handlers = new Hashtable();

// Przetwarzamy dane XML o konfiguracji

parseConfiguration();

}

/**

* <p>

* Zwraca numer portu, na którym nasłuchuje serwer.

* </p>

*

* @return <code>int</code> - port serwera.

*/

public int getPortNumber() {

return portNumber;

}

/**

* <p>

* Ustawia numer portu, na którym nasłuchujemy.

* </p>

*

* @param portNumber <code>int</code> - port do nasłuchiwania.

*/

public void setPortNumber(int portNumber) {

this.portNumber = portNumber;

}

/**

* <p>

* Zwraca nazwę hosta, w którym uruchomiono serwer.

* </p>

*

* @return <code>String</code> - nazwa hosta serwera.

*/

public String getHostname() {

return hostname;

}

/**

* <p>

* Ustawia nazwę hosta, w którym działa serwer.

* </p>

*

* @param hostname <code>String</code> nazwa hosta serwera.

*/

public void setHostname(String hostname) {

this.hostname = hostname;

}

/**

* <p>

* Zwraca klasę sterownika SAX do załadowania.

* </p>

*

* @return <code>String</code> - nazwa klasy sterownika SAX.

*/

public String getDriverClass() {

return driverClass;

}

/**

* <p>

* Ustawia klasę sterownika do przetwarzania.

* </p>

*

* @param driverClass <code>String</code> nazwa klasy parsera.

*/

public void setDriverClass(String driverClass) {

this.driverClass = driverClass;

}

/**

* <p>

* Zwraca procedury obsługi do zarejestrowania przez serwer.

* </p>

*

* @return <code>Hashtable</code> - struktura procedur obsługi.

*/

public Hashtable getHandlers() {

return handlers;

}

/**

* <p>

* Ustawia procedury obsługi do zarejestrowania.

* </p>

*

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

*/

public void setHandlers(Hashtable handlers) {

this.handlers = handlers;

}

/**

* <p>

* Przetwarzamy informacje o konfiguracji XML i

* udostępniamy je klientom.

* </p>

*

* @throws <code>IOException</code> w razie wystąpienia błędu.

*/

private void parseConfiguration() throws IOException {

try {

// Żądamy implementacji DOM i parsera Xerces.

Builder builder =

new DOMBuilder("org.jdom.adapters.XercesDOMAdapter");

// Pobieramy dokument konfiguracyjny, wykonując sprawdzanie poprawności.

doc = builder.build(in);

// Pobieramy element główny.

Element root = doc.getRootElement();

// Uzyskujemy przestrzeń nazw JavaXML.

Namespace ns = Namespace.getNamespace("JavaXML",

"http://www.oreilly.com/catalog/javaxml/");

// Ładujemy nazwę hosta, port i klasę procedury obsługi.

hostname = root.getChild("hostname", ns).getContent();

driverClass = root.getChild("parserClass", ns).getContent();

portNumber =

Integer.parseInt(root.getChild("port", ns).getContent());

// Pobieramy procedury obsługi.

List handlerElements =

root.getChild("xmlrpc-server", ns)

.getChild("handlers", ns)

.getChildren("handler", ns);

Iterator i = handlerElements.iterator();

while (i.hasNext()) {

Element current = (Element)i.next();

handlers.put(current.getChild("identifier", ns).getContent(),

current.getChild("class", ns).getContent());

}

} catch (JDOMException e) {

throw new IOException(e.getMessage());

}

}

/**

* <p>

* Tutaj zachowujemy bieżący stan do pliku konfiguracyjnego XML-RPC.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(String filename)

throws IOException {

saveConfigurationFromScratch(new FileOutputStream(filename));

}

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

// Do zaimplementowania

}

}

Oprócz nowych metod została utworzona zmienna przynależna obiektu Document, doc, którą wy­korzystujemy zarówno do odczytywania, jak i do zapisywania dokumentu konfiguracyjnego. Sensowniej jest przechowywać odwołanie do obiektu JDOM Document, niż ładować je od nowa w metodzie saveConfiguration(). Metoda parseConfiguration() ładuje teraz dane do zmiennej doc, którą następnie można wykorzystać ponownie w metodzie saveConfigu­ration().

Wyświetlanie informacji o konfiguracji

Skoro klasa XmlRpcConfiguration jest już gotowa, należy teraz utworzyć interfejs do prze­glądania i wprowadzania zmian w konfiguracji. Odpowiednim rozwiązaniem jest tutaj interfejs w postaci serwletu Javy — uzyskujemy dostęp do prostego modelu żądań i odpowiedzi i nie mu­simy specjalnie zagłębiać się w programowanie sieciowe. Interfejs taki umożliwia również admi­nistrowanie zdalne, poprzez dowolną przeglądarkę internetową. Najpierw stworzymy tę część serwleta, która odpowie na proste żądanie z przeglądarki (wysłane z wykorzystaniem metody GET) i wyświetli bieżącą konfigurację. Wszystko to wymaga stworzenia egzemplarza klasy Xml­RpcConfiguration i wyświetlenia formularza HTML z informacjami pobranymi z tej klasy. Ponieważ są to podstawy Javy i serwletów, kod przedstawiony jest w przykładzie 12.2 bez dalszych wyjaśnień. Jeśli Czytelnik chciałby lepiej zapoznać się z interfejsem serwletów w Javie, warto zajrzeć do książki Jasona Huntera Java Servlet Programming, wyd. O'Reilly & Associates.

Przykład 12.2. Serwlet w Javie wyświetlający informacje o konfiguracji XML-RPC

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Enumeration;

import java.util.Hashtable;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.oreilly.xml.XmlRpcConfiguration;

/**

* <b><code>XmlRpcConfigurationServlet</code></b> to narzędzie administracyjne

* umożliwiające zachowywanie zmian w pliku konfiguracyjnym XML.

*

* @author Brett McLaughlin

* @version 1.0

*/

public class XmlRpcConfigurationServlet extends HttpServlet {

/** Składujemy plik konfiguracyjny XML-RPC jako stałą */

private static final String CONFIG_FILENAME =

"/usr/local/projects/javaxml/xmlrpc/xmlrpc.xml";

/**

* Przekazujemy działanie serwletowi (i metodzie

* <code>{@link #doPost()}</code>).

* W Servlet API 2.1 lub 2.2 można to zrobić programowo,

* ale chcemy, aby ten przykład działał także z interfejsem Servlet 2.0.

*/

private static final String FORM_ACTION =

"/javaxml/servlet/XmlRpcConfigurationServlet";

/** Obiekt konfiguracyjny, na którym pracujemy */

XmlRpcConfiguration config;

/**

* <p>

* Żądania GET są otrzymywane, gdy klient chce przejrzeć bieżącą konfigurację.

* Umożliwiamy tylko podgląd danych. Wygenerowany formularz HTML

* jest ponownie przekazywany serwletowi metodą POST, co powoduje wywołanie

* metody <code>{@link #doPost}</code>.

* </p>

*/

public void doGet(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

// Ładujemy informacje o konfiguracji za pomocą naszej klasy narzędziowej.

config = new XmlRpcConfiguration(CONFIG_FILENAME);

// Wyświetlamy interfejs HTML.

out.println("<html><head>");

out.println("<title>Konfiguracja XML-RPC</title>");

out.println("</head><body>");

out.println("<h2 align=\"center\">Konfiguracja XML-RPC</h2>");

out.println("<form action=\"" + FORM_ACTION + "\" " +

"method=\"POST\">");

out.println("<b>Nazwa hosta:</b> ");

out.println("<input type=\"text\" " +

"name=\"hostname\" " +

"value=\"" + config.getHostname() +

"\" />");

//out.println("<br />");

out.println("&nbsp;&nbsp;&nbsp;&nbsp;");

out.println("<b>Numer portu:</b> ");

out.println("<input type=\"text\" " +

"name=\"port\" " +

"value=\"" + config.getPortNumber() +

"\" />");

out.println("<br />");

out.println("<b>Klasa sterownika SAX:</b> ");

out.println("<input type=\"text\" " +

"name=\"driverClass\" size=\"50\"" +

"value=\"" + config.getDriverClass() +

"\" />");

out.println("<br />");

out.println("<br />");

out.println("<h3 align=\"center\">Procedury obsługi XML-RPC</h3>");

// Wyświetlamy bieżące procedury obsługi.

Hashtable handlers = config.getHandlers();

Enumeration keys = handlers.keys();

int index = 0;

while (keys.hasMoreElements()) {

String handlerID =

(String)keys.nextElement();

String handlerClass =

(String)handlers.get(handlerID);

out.println("<b>Identyfikator:</b> ");

out.println("<input type=\"text\" " +

"value=\"" + handlerID + "\" " +

"name=\"handlerID\" /> ");

out.println("<b>Klasa:</b> ");

out.println("<input type=\"text\" " +

"value=\"" + handlerClass + "\" " +

"size=\"30\" " +

"name=\"handlerClass\" /> ");

out.println("<br />");

index++;

}

// Wyświetlamy puste pola na dodatkowe procedury obsługi

for (int i=0; i<3; i++) {

out.println("<b>Identyfikator:</b> ");

out.println("<input type=\"text\" " +

"name=\"handlerID\" /> ");

out.println("<b>Klasa:</b> ");

out.println("<input type=\"text\" " +

"size=\"30\" " +

"name=\"handlerClass\" /> ");

out.println("<br />");

index++;

}

out.println("<br /><center>");

out.println("<input type=\"submit\" value=\"Zapisz zmiany\" />");

out.println("</center>");

out.println("</form></body></html>");

out.close();

}

/**

* <p>

* Ta metoda otrzymuje żądania zmiany konfiguracji poprzez metodę

* <code>{@link #doGet}</code>. Do aktualizacji pliku konfiguracyjnego

* zostanie ponownie wykorzystana klasa narzędziowa. Zapisem do pliku

* zajmie się obiekt <code>{@link XmlRpcConfiguration}</code>.

* </p>

*/

public void doPost(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

// Aktualizacja informacji konfiguracyjnych.

if (config == null) {

config = new XmlRpcConfiguration(CONFIG_FILENAME);

}

// Zachowujemy nazwę hosta.

String hostname =

req.getParameterValues("hostname")[0];

if ((hostname != null) && (!hostname.equals(""))) {

config.setHostname(hostname);

}

// Zachowujemy numer portu.

int portNumber;

try {

portNumber =

Integer.parseInt(

req.getParameterValues("port")[0]);

} catch (Exception e) {

portNumber = 0;

}

if (portNumber > 0) {

config.setPortNumber(portNumber);

}

// Zachowujemy klasę sterownika SAX.

String driverClass =

req.getParameterValues("driverClass")[0];

if ((driverClass != null) && (!driverClass.equals(""))) {

config.setDriverClass(driverClass);

}

// Zachowujemy procedury obsługi.

String[] handlerIDs =

req.getParameterValues("handlerID");

String[] handlerClasses =

req.getParameterValues("handlerClass");

Hashtable handlers = new Hashtable();

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

handlers.put(handlerIDs[i], handlerClasses[i]);

}

config.setHandlers(handlers);

try {

// Żądamy zapisu zmian do pliku.

config.saveConfiguration(CONFIG_FILENAME);

} catch (IOException e) {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

out.println(e.getMessage());

return;

}

// Wyświetlamy komunikat potwierdzający.

res.setContentType("text/html");

PrintWriter out = res.getWriter();

out.println("Zmiany zapisane <br />");

out.println("<a href=\"" + FORM_ACTION +

"\">Powrót do interfejsu administracyjnego" +

"</a>");

out.close();

}

}

Zostały tutaj wykorzystane informacje mówiące o tym, że wstępne żądania będą nadchodziły do serwleta poprzez metodę GET; natomiast przekazywanie naszego formularza będzie odbywało się poprzez POST. Tak więc po otrzymaniu żądania GET (metoda doGet()) wyświetlamy infor­ma­cje o konfiguracji, zaś po otrzymaniu żądania POST (metoda doPost()) aktualizujemy zmiany. Jeśli żądanie zostało wysłane metodą GET, w przeglądarce WWW zostanie wyświetlona bieżąca konfiguracja (rysunek 12.1).

Po kliknięciu przycisku wysyłany jest formularz HTML, który otrzymuje ten sam serwlet (tym razem metodą POST). Metoda doPost() odczytuje otrzymane parametry i za pomocą mutato­rów klasy XmlRpcConfiguration aktualizuje konfigurację. Potem wywoływana jest metoda saveConfiguration() z parametrem w postaci tego samego pliku konfiguracyjnego. W na­stępnej części Czytelnik dowie się, jak przetworzyć zaktualizowane dane do postaci obiektu JDOM Document, a następnie zapisać do pliku konfiguracyjnego XML. Na koniec nasz serwlet wyświe­tla odsyłacz HTML, który (poprzez metodę GET) przenosi użytkownika z powrotem do formu­la­rza konfiguracyjnego. Zaktualizowane dane są wyświetlane i cały proces można rozpocząć od nowa.

Modyfikacja danych

Teraz trzeba jeszcze tylko stworzyć kod wewnątrz metody saveConfiguration(), który „zajmie się” aktualizacją dokumentu XML po zmodyfikowaniu wartości zmiennych. Cały ten proces można powierzyć całkowicie interfejsowi JDOM za pomocą obiektu Document (który załadowaliśmy i do którego zachowaliśmy odwołanie przy przetwarzaniu dokumentu XML) oraz dostarczony OutputStream (który, w naszym przypadku, stanowi tak naprawdę File­Out­putStream odnoszący się do pliku). Po wykonaniu zmian w konfiguracji obiekt Document

0x01 graphic

Rysunek 12.1. Interfejs HTML do przeglądania, modyfikowania i konfiguracji

musi zostać zaktualizowany, a zmiany zapisane do pliku. W innych aplikacjach zmodyfikowany Document mógłby zostać przekształcony za pomocą XSLT i wysłany jako HTML lub inny język znaczników, ewentualnie przekazany poprzez sieć do innej aplikacji.

Aktualizacja konfiguracji

Aby aplikacja była całkowicie funkcjonalna, trzeba jeszcze tylko dodać kod do metody save­Con­figuration(), który pobierze jako argument OutputStream. Obiekt Document jest już zachowany, pozostało więc ustawić zawartość poszczególnych elementów zmodyfikowanych me­todą setContent(), dostępną w egzemplarzach Element. Najpierw zajmujemy się ele­men­­­tami hostname, port i parserClass, zagnieżdżonymi bezpośrednio w elemencie głównym:

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.IOException;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.List;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.JDOMException;

import org.jdom.Namespace;

import org.jdom.input.Builder;

import org.jdom.input.DOMBuilder;

import org.jdom.output.XMLOutputter;

...

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

try {

Element root = doc.getRootElement();

// Pobieramy przestrzeń nazw JavaXML.

Namespace ns = Namespace.getNamespace("JavaXML",

"http://www.oreilly.com/catalog/javaxml/");

// Aktualizujemy nazwę hosta.

root.getChild("hostname", ns)

.setContent(hostname);

// Aktualizujemy klasę sterownika SAX.

root.getChild("parserClass", ns)

.setContent(driverClass);

// Aktualizujemy numer portu.

root.getChild("port", ns)

.setContent(portNumber + "");

// Prostszy sposób usuwania i ponownego dodawania procedur.

Element handlersElement =

root.getChild("xmlrpc-server", ns)

.getChild("handlers", ns);

handlersElement.removeChildren("handler", ns);

// Przekazujemy dokument na wyjście, standardowy moduł formatujący

XMLOutputter fmt = new XMLOutputter();

fmt.output(doc, out);

} catch (JDOMException e) {

// Komunikujemy o błędzie.

throw new IOException(e.getMessage());

}

}

Podobnie jak we wcześniejszych przykładach, użyta została tutaj wersja getChild(), która po­bie­­ra­ zarówno Namespace, jak i lokalną nazwę elementu. Ponadto wykonuje proste łączenie łańcucha String z numerem portu, tak aby uzyskać poprawny parametr setContent(). Na koniec na­leży zapisać zmiany do dostarczonego strumienia OutputStream za pomocą klasy pomocniczej XMLOutputter, co gwarantuje, że wprowadzone zmiany zostaną odzwierciedlone w pliku.

Dodanie informacji o procedurach obsługi to zadanie nieco innej natury. Zamiast wykonywać ite­ra­cję po bieżących elementach handler i zamieniać je na inne, a następnie dodawać lub usuwać pro­ce­dury obsługi tak, aby ich lista zgadzała się z tą dostarczoną przez użytkownika, prościej będzie usu­nąć je wszystkie i na powrót utworzyć nową listę. Przede wszystkim importujemy klasę Enume­ra­tion — posłuży ona do obsługi tablicy Hashtable zawierającej procedury do dodania:

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.util.Enumeration;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.List;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.JDOMException;

import org.jdom.Namespace;

import org.jdom.input.Builder;

import org.jdom.input.DOMBuilder;

Teraz z elementu handlers można usunąć wszystkie elementy handler. W tym przypadku bezpiecznie byłoby wywołać removeChildren() bez argumentów (usunięte zostałyby wszy­st­kie elementy potomne), jednak bardziej przejrzystym rozwiązaniem będzie jawne usunięcie po­szcze­gólnych elementów; gdyby kiedyś do elementu handlers zostały dodane nowe elementy, kod będzie działał poprawnie bez konieczności wprowadzania zmian. Po usunięciu elementów potomnych „przebiegamy” po procedurach obsługi zdefiniowanych przez użytkownika i każdą z nich dodajemy do obiektu JDOM Document:

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

try {

Element root = doc.getRootElement();

// Pobieramy przestrzeń nazw JavaXML.

Namespace ns = Namespace.getNamespace("JavaXML",

"http://www.oreilly.com/catalog/javaxml/");

// Aktualizujemy nazwę hosta.

root.getChild("hostname", ns)

.setContent(hostname);

// Aktualizujemy klasę sterownika SAX.

root.getChild("parserClass", ns)

.setContent(driverClass);

// Aktualizujemy numer portu.

root.getChild("port", ns)

.setContent(portNumber + "");

// Prostszy sposób usuwania i ponownego dodawania procedur.

Element handlersElement =

root.getChild("xmlrpc-server", ns)

.getChild("handlers", ns);

handlersElement.removeChildren("handler", ns);

// Dodanie nowych procedur obsługi.

Enumeration handlerIDs = handlers.keys();

while (handlerIDs.hasMoreElements()) {

String handlerID =

(String)handlerIDs.nextElement();

// Sprawdzamy, czy przypadkiem nie rejestrujemy pustego łańcucha.

if (handlerID.trim().equals("")) {

continue;

}

String handlerClass =

(String)handlers.get(handlerID);

handlersElement.addChild(

new Element("handler", ns)

.addChild(

new Element("identifier", ns)

.setContent(handlerID))

.addChild(

new Element("class", ns)

.setContent(handlerClass))

);

}

// Przekazujemy dokument na wyjście, standardowy moduł formatujący

XMLOutputter fmt = new XMLOutputter();

fmt.output(doc, out);

} catch (JDOMException e) {

// Log an error

throw new IOException(e.getMessage());

}

}

Po wkompilowaniu powyższych zmian do klasy XmlRpcConfiguration, można przetestować aplikację. Na rysunku 12.2 pokazano przykładowe dane wejściowe wprowadzone do serwleta Xml­RpcConfigurationServlet.

Po przekazaniu takiego formularza do programu zmiany zostają zachowane, a wynikowy plik xmlrpc.xml powinien wyglądać tak jak w przykładzie 12.3.

Przykład 12.3. Zmodyfikowany plik konfiguracyjny XML-RPC

<?xml version="1.0" encoding="UTF-8"?>

<JavaXML:xmlrpc-config

xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"

>

<JavaXML:hostname>www.jdom.org</JavaXML:hostname>

<JavaXML:port type="unprotected">1310</JavaXML:port>

<JavaXML:parserClass>

0x01 graphic

Rysunek 12.2. Przykładowe dane wejściowe przekazane do serwleta XmlRpcConfigurationServlet

oracle.xml.parser.v2.DOMParser

</JavaXML:parserClass>

<JavaXML:xmlrpc-server>

<JavaXML:handlers>

<JavaXML:handler>

<JavaXML:identifier>newUserGreeting</JavaXML:identifier>

<JavaXML:class>NewUserGreetingService</JavaXML:class>

</JavaXML:handler>

<JavaXML:handler>

<JavaXML:identifier>mailingList</JavaXML:identifier>

<JavaXML:class>MailingListHandler</JavaXML:class>

</JavaXML:handler>

<JavaXML:handler>

<JavaXML:identifier>cvsUpdate</JavaXML:identifier>

<JavaXML:class>CVSUpdateHandler</JavaXML:class>

</JavaXML:handler>

</JavaXML:handlers>

</JavaXML:xmlrpc-server>

</JavaXML:xmlrpc-config>

Teraz można byłoby już uruchomić klasę com.oreilly.xml.LightweightXml­RpcSer­ver i — przy założeniu, że mamy parser Oracle i klasy procedur obsługi w ścieżce dostępu do klas (a także dostęp do http://www.jdom.org, którego Czytelnik prawdopodobnie nie posiada!)
— możemy używać serwera i klientów XML-RPC skonfigurowanych według podanych informacji.

Budowanie dokumentu XML od podstaw

Zawsze istnieje możliwość zbudowania dokumentu XML od zera. Możliwość ta jest przydatna w sytuacji, kiedy nie istnieje oryginalny dokument albo jeśli dokument oryginalny jest tak zło­żony, że prościej jest zbudować go od nowa, niż modyfikować. Budowanie nowego dokumentu XML przydaje się również wtedy, gdy dane wyjściowe aplikacji powinny mieć postać danych XML nadających się do wykorzystania przez inny składnik aplikacji (np. w aplikacjach typu firma-firma, przedstawionych w rozdziale 13.). Na szczęście tworzenie nowego dokumentu XML nie wymaga wprowadzania bardzo poważnych zmian w kodzie JDOM. Interfejs JDOM do tworzenia obiektu Document wykorzystuje interfejsy SAX i DOM (lub inne implementacje), a więc po­zostajemy „oddzieleni” od samego procesu budowania dokumentu. Jeśli konieczne jest utworzenie nowego dokumentu XML, klasy Builder nie są wykorzystywane. Buduje się nowy Document, posiadający nowy element główny; następnie dodaje się inne elementy i dowolnie modyfikuje za­wartość, po czym wysyła na wyjście za pomocą klasy Formatter. Prawda, że proste? W przy­kładzie 12.4 pokazano metodę saveConfiguration() zmodyfikowaną tak, że tworzy nowy Document do zachowania jako plik konfiguracyjny XML-RPC.

Przykład 12.4. Budowanie dokumentu XML od podstaw

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.util.Enumeration;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.List;

import org.jdom.DocType;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.JDOMException;

import org.jdom.Namespace;

import org.jdom.input.Builder;

import org.jdom.input.DOMBuilder;

...

/**

* <p>

* Tutaj zapisujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfigurationFromScratch(OutputStream out)

throws IOException {

// Pobieramy przestrzeń nazw JavaXML.

Namespace ns = Namespace.getNamespace("JavaXML",

"http://www.oreilly.com/catalog/javaxml/");

// Tworzymy element główny.

Element root = new Element("xmlrpc-config", ns);

Document doc = new Document(root);

doc.setDocType(new DocType("JavaXML:xmlrpc-config",

"DTD/XmlRpc.dtd"));

root.addChild(new Element("hostname", ns)

.setContent(hostname))

.addChild(new Element("port", ns)

.addAttribute("type", "unprotected")

.setContent(portNumber + ""))

.addChild(new Element("parserClass", ns)

.setContent(driverClass));

Element handlersElement = new Element("handlers", ns);

Enumeration e = handlers.keys();

while (e.hasMoreElements()) {

String handlerID = (String)e.nextElement();

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

handlersElement.addChild(new Element("handler", ns)

.addChild(new Element("identifier", ns)

.setContent(handlerID))

.addChild(new Element("class", ns)

.setContent(handlerClass))

);

}

root.addChild(new Element("xmlrpc-server", ns)

.addChild(handlersElement));

// Przekazujemy dokument na wyjście, standardowy moduł formatujący.

XMLOutputter fmt = new XMLOutputter();

fmt.output(doc, out);

}

Najpierw została dodana dodatkowa instrukcja importująca org.jdom.DocType. Ponieważ ele­ment tworzony jest od początku, trzeba wstawić odwołanie do odpowiedniej definicji DTD. Usu­nięty został także cały blok kodu try/catch, który wcześniej znajdował się w tym miejscu. Wy­jątek JDOMException, który wcześniej przechwytywaliśmy, zgłaszany był przez klasy Builder; XMLOutputter zgłasza w razie problemów tylko jeden wyjątek: IOException. Następnie należy utworzyć element główny, JavaXML:xmlrpc-config, po czym użyć go do two­rzenia nowego obiektu JDOM Document. Elementy tekstowe (nazwa hosta, port i klasa SAX) doda­wa­ne są jako dodatkowe obiekty Element do elementu głównego, a ich zawartość teks­towa jest odpowiednio ustawiana (dodany został także atrybut do elementu JavaXML:port). Na koniec na­leży „przejść” po wszystkich procedurach obsługi wpisanych przez użytkownika, po­dob­nie jak to było robione w poprzedniej wersji metody. Każda procedura obsługi dodawana jest jako Element JavaXML:handler, a ten z kolei dodawany jest do elementu JavaXML:hand­lers. Cały ten zbiór jest znów dodawany do węzła JavaXML:xmlrpc-server, który z kolei do­da­wa­ny jest do elementu głównego dokumentu. Na koniec Document przekazywany jest do po­danego strumienia OutputStream i... to wszystko!

W zależności od użytego interfejsu API, proces tworzenia dokumentu XML — czy to w pamięci, czy w systemie plików — może być tak prosty, jak modyfikacja gotowego dokumentu (jak w tym przykładzie). Ale nie zawsze tak jest. Niektóre interfejsy wymagają tworzenia specyficznych dla producenta elementów i atrybutów. W ostatniej części tego rozdziału zostaną porównane różne interfejsy Javy do tworzenia danych XML i poruszone inne ważne zagadnienia związane z wy­ko­nywaniem mutacji XML-a z poziomu tego języka.

Praktyka

Tradycyjnie już, na zakończenie rozdziału zostaną omówione zagadnienia związane z używaniem opisywanych narzędzi w warunkach rzeczywistych. W tym rozdziale zostały poruszone nastę­pu­ją­ce kwestie: tworzenie danych XML, alternatywy względem JDOM-a przy budowaniu danych XML oraz obsługa nieaktualnych odwołań do klasy narzędziowej XmlRpcConfiguration.

Wątkowanie, pisanie i arytmetyka

Choć metoda saveConfiguration() oraz sposób, w jaki pozwala ona zapisać plik XML, zo­stały omówione bardzo pospiesznie, Czytelnik być może zauważył pewien istotny zapis w de­kla­racji metody:

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

// implementacja metody

}

Słowo kluczowe synchronized gwarantuje, że zanim dane konfiguracyjne zostaną zapisane, obiekt XmlRpcConfiguration zostanie opatrzony blokadą. Jest to szczególnie istotne w przy­padku korzystania z XML-a, ponieważ interfejsy takie jak JDOM mogą być tworzone na im­ple­mentacjach okresowo przeładowujących dane, z których korzystają; innymi słowy, zmiany wykonane przez inne programy mogłyby spowodować poważne błędy lub zniszczenie danych XML, jeśli jednocześnie ta metoda zapisywałaby do nich swoje informacje.

Co więcej, należy bardzo uważać, kiedy XML zapisuje wiele apli­kacji do tego samego źródła da­nych. Na potrzeby pesymistycznego i optymistycznego blokowania, współużytkowania danych oraz rozwiązania wielu innych złożonych zagadnień napisano całe systemy bazodanowe. XML to świe­tny sposób na uzyskanie przenośności danych, niemniej tym powyższych problemów nie roz­wią­­zuje. Jest na to wszystko bardzo praktyczny sposób — pisanie do pliku XML powinno odby­wać się poprzez jeden wspólny „punkt wejściowy”. W przypadku mutowania danych istnienie wielu takich punktów może spowodować występowanie dziwnych błędów i w ogóle dużo zamieszania.

JDOM, SAX lub DOM — jeszcze raz

W rozdziale 11. zostały omówione alternatywy względem JDOM-a przy odczytywaniu danych XML. Teraz znowu zostaną one przedstawione, ale w odniesieniu do zapisu i modyfikacji danych. Dzięki temu Czytelnik uzyska kompletny obraz interfejsów programistycznych Javy i nie będzie miał pro­blemu z wyborem takiego interfejsu pod kątem konkretnego projektu.

Dla porównania — poniżej przedstawiona jest metoda saveConfiguration() z poprzed­nie­go rozdziału, wykorzystująca JDOM:

/**

* <p>

* Tutaj zapisujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

// Pobieramy przestrzeń nazw JavaXML.

Namespace ns = Namespace.getNamespace("JavaXML",

"http://www.oreilly.com/catalog/javaxml/");

// Tworzymy element główny.

Element root = new Element("xmlrpc-config", ns);

Document doc = new Document(root);

doc.setDocType(new DocType("JavaXML:xmlrpc-config",

"DTD/XmlRpc.dtd"));

root.addChild(new Element("hostname", ns)

.setContent(hostname))

.addChild(new Element("port", ns)

.addAttribute("type", "unprotected")

.setContent(portNumber + ""))

.addChild(new Element("parserClass", ns)

.setContent(driverClass));

Element handlersElement = new Element("handlers", ns);

Enumeration e = handlers.keys();

while (e.hasMoreElements()) {

String handlerID = (String)e.nextElement();

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

handlersElement.addChild(new Element("handler", ns)

.addChild(new Element("identifier", ns)

.setContent(handlerID))

.addChild(new Element("class", ns)

.setContent(handlerClass))

);

}

root.addChild(new Element("xmlrpc-server", ns)

.addChild(handlersElement));

// Przekazujemy dokument na wyjście, standardowy moduł formatujący.

XMLOutputter fmt = new XMLOutputter();

fmt.output(doc, out);

}

Poniżej przedstawione są sposoby wykonania tego samego zadania za pomocą interfejsów SAX i DOM.

SAX

W przypadku SAX-a sprawa jest prosta — mówiąc krótko, za pomocą tego interfejsu nie da się zmodyfikować danych XML. SAX oferuje podejście do danych XML oparte na zdarzeniach, a więc nadaje się tylko do przetwarzania gotowego dokumentu. Zdefiniowane w tym interfejsie wy­wołania służą tylko do tego celu. SAX nie jest „świadomy” całościowej postaci dokumentu XML, nie potrafi więc także całej tej postaci zmienić. To właśnie z tego powodu DOM zyskał dużą popularność — do niedawna stanowił jedyny sposób tworzenia danych XML z programów w Javie bez konieczności bezpośredniego zapisywania XML-a za pomocą strumieni.

DOM

Model DOM umożliwia tworzenie i modyfikowanie dokumentu XML-a z języka Java. Jednakże cha­rakteryzuje go o wiele bardziej ścisłe przestrzeganie struktury drzewiastej dokumentu. Po pierw­sze, w interfejsie tym każdy fragment drzewa postrzegany jest jako węzeł Node (warto przy­pom­nieć sobie informacje z rozdziału 7.). Ponieważ wszystkie węzły stanowią część drzewa, przy tworzeniu węzła Node konieczne jest skojarzenie go z konkretnym drzewem DOM, reprezen­to­wa­nym przez obiekt DOM Document. Aby zagwarantować, że model ten jest przestrzegany, nie stworzono żadnego me­cha­nizmu do uzyskiwania egzemplarza Node bezpośrednio; zamiast tego ope­racje createEle­ment(), createAttriute() oraz inne operacje na węzłach zostały zde­fi­nio­wane w in­terfejsie Document. Trzeba również pamiętać, że w takim ścisłym modelu drzewa wszystko postrzegane jest jako Node, w tym dane tekstowe. Nie istnieje więc pojęcie zawartości elementu — Element DOM posiada węzły potomne, z których część jest węzłami Text Node. Tak więc ustawienie wartości elementu wymaga skorzystania z metody createTextNode(), a na­stępnie dodania tak uzyskanego węzła Text Node do macierzystego elementu Element. Oczy­wiście, na obie­kcie Document wywoływana jest jeszcze sama metoda createTextNode(), zapewniająca odpowiednie skojarzenie elementu z drzewem DOM.

0x01 graphic

To może wydawać się nieco zagmatwane — utworzenie elementu XML wymaga „za­przę­żenia” obiektów DOM Element Node, DOM Text Node oraz DOM Do­cument. To niezbyt atrakcyjne rozwiązanie, szczególnie z perspektywy nowicjuszy w ję­zyku XML (którzy zwykle próbują zmienić zawartość tekstową elementu metodą setNodeValue() i otrzymują wyniki niezgodne z oczekiwaniami). Trzeba nie­us­tan­nie pamiętać, że w interfejsie DOM bardzo ściśle przestrzega się struktury drze­wiastej.

Pamiętając o tym, spróbujmy zbudować dokument XML z danych dostarczonych przez użytkow­nika w aplikacji XmlRpcConfigurationServlet.

Można byłoby oczekiwać, że będziemy powtarzali proces odnajdywania każdego elementu zawie­ra­jącego dane i zmieniania zawartości jego węzłów tekstowych, podobnie jak w naszej pierwszej metodzie saveConfiguration(), wykorzystującej JDOM. To oczywiście możliwe (DOM udo­stępnia w odniesieniu do węzłów tekstowych metodę setValue()), ale nie jest to sposób ani naj­łat­wiej­szy, ani najszybszy. Zamiast tego użyjemy metod z rodziny createXXX(), zde­fi­nio­wa­nych w interfejsie DOM Document. Mamy metody dla każdego typu węzła DOM Node, takie jak createElement(), createTextNode() i createAttriute(). W miarę jak two­rzo­ne są te wszystkie obiekty, zostają one przypisane obiektowi Document. W ten sposób zacho­wana jest odpowiednia relacja „starszeństwa” pomiędzy każdym utworzonym węzłem a drzewem DOM, do którego on należy.

W ten sposób tworzymy wszystkie wymagane elementy i dane. Ale to dopiero połowa zadania; każdy element oraz jego dane muszą potem zostać wstawione do drzewa DOM i skonsolidowane do postaci jednej, poprawnej hierarchii. Najprostszym sposobem wstawienia węzła Node do drze­wa jest wykonanie metody appendChild() na węźle macierzystym.

W ten sposób można już zbudować kompletny dokument, rozpoczynając od elementu głównego. Na­stępnie zamienimy stary element główny na nowy i uzyskamy zaktualizowany model drzewiasty.

0x01 graphic

Trzeba koniecznie zrozumieć różnice pomiędzy metodami createXXX() i ap­pend­Node(). Pierwsza metoda zwraca Node powiązany z obiektem DOM Docu­ment, ale nie wstawia tego węzła do dokumentu; na określonym elemencie macierzystym trze­ba jeszcze wykonać metodę appendNode(). Nieumiejętność rozróżnienia tych dwóch metod może spowodować stworzenie wielkiej liczby węzłów skojarzonych z pe­wnym obiektem DOM Document, przy czym samo drzewo DOM pozostanie puste.

Nie musimy wykonywać skomplikowanego przeszukiwania i pozyskiwania elementów i danych, a ponadto takie podejście jest często o wiele szybsze. Przeszukiwanie drzewa DOM, szczególnie jeśli jest ono pokaźnych rozmiarów, może zająć sporo czasu procesora; zbudowanie drzewa XML począwszy od elementu głównego to sposób o wiele szybszy. Kod wykonujący takie zadanie przed­stawiono poniżej:

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>, za pomocą DOM-a.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

String NAMESPACE_URI = "http://www.oreilly.com/catalog/javaxml/";

// Zakładamy, że obiekt DOM Document został załadowany w metodzie

// parseConfiguration() i zachowany w zmiennej o nazwie

// <code>doc</code>.

Element oldRoot = doc.getDocumentElement();

Element newRoot =

doc.createElementNS(NAMESPACE_URI, "JavaXML:xmlrpc-config");

// Obsługa nazwy hosta.

Element hostnameNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:hostname");

hostnameNode.appendChild(

doc.createTextNode(hostname));

newRoot.appendChild(hostnameNode);

// Obsługa numeru portu.

Element portNumberNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:port");

portNumberNode.appendChild(

doc.createTextNode(portNumber + ""));

portNumberNode.setValue("type", "unprotected");

newRoot.appendChild(portNumberNode);

// Obsługa klas sterownika SAX.

Element saxDriverNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:parserClass");

saxDriverNode.appendChild(

doc.createTextNode(driverClass));

newRoot.appendChild(saxDriverNode);

Element serverNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:xmlrpc-server");

Element handlersNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:handlers");

// Obsługa procedur obsługi.

Enumeration handlerIDs = handlers.keys();

while (handlerIDs.hasMoreElements()) {

String handlerID = (string)handlerIDs.nextElement();

String handlerClass = (string)handles.get(handlerID);

Element handlerIDNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:identifier");

handlerIDNode.appendChild(

doc.createTextNode(handlerID));

Element handlerClassNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:class");

handlerClassNode.appendChild(

doc.createTextNode(handlerClass));

Element handlerNode =

doc.createElementNS(NAMESPACE_URI, "JavaXML:handler");

handlerNode.appendChild(handlerIDNode);

handlerNode.appendChild(handlerClassNode);

handlersNode.appendChild(handlerNode);

}

serverNode.appendChild(handlersNode);

newRoot.appendChild(serverNode);

doc.replaceChild(newRoot, oldRoot);

// Serializacja drzewa DOM.

}

Do stworzenia dokumentu XML „rozumiejącego” przestrzenie nazw wykorzystujemy metody DOM Level 2. Metoda createElementNS() pobiera przestrzeń nazw URI oraz pełną nazwę nowego elementu. Pełna nazwa zawiera przedrostek przestrzeni nazw, co gwarantuje, że zarówno przed­rostek przestrzeni nazw, jak i identyfikator URI przestrzeni zostaną razem włączone (lub wy­łączone) i powstanie poprawnie sformatowany dokument XML.

Kolejne zadanie, które — podobnie jak uzyskiwanie obiektu DOM Document — nie zostało opi­sa­ne w specyfikacji DOM, to serializacja drzewa DOM. Aby osiągnąć serializację, trzeba spraw­dzić, czy określony producent oferuje klasę pomocniczą służącą do przeprowadzenia takiej serializacji. Po jej wyszukaniu zazwyczaj wystarczy tylko ją zaimportować i przekazać jej egzem­plarz OutputStream lub PrintWriter. W Apache Xerces odpowiednia klasa nosi nazwę org.apache.xml.serialize.XMLSerializer. Klasy tej użyjemy do zapisu do stru­mie­nia OutputStream, który został przekazany przez naszą metodę saveConfiguration():

/**

* <p>

* Tutaj zachowujemy stan bieżący do określonego strumienia

* <code>OutputStream</code>, za pomocą DOM-a.

* </p>

*

* @throws <code>IOException</code> - w razie błędów przy zapisywaniu.

*/

public synchronized void saveConfiguration(OutputStream out)

throws IOException {

// Modyfikacja obiektu Document.

doc.replaceChild(newRoot, oldRoot);

// Serializacja drzewa DOM.

org.apache.xml.serialize.XMLSerializer out =

new org.apache.xml.serialize.XMLSerializer(out, null);

out.serialize(doc);

}

Klasa Apache XMLSerializer posiada szereg różnych konstruktorów; konstruktor zastoso­wa­ny tutaj pobiera OutputStream oraz format, jaki ma zostać użyty do uzyskania danych wyni­ko­wych. Format określamy jako null, co oznacza, że zdajemy się na format domyślny. Metoda se­rialize() pobiera jako parametry wejściowe obiekty Document, Element lub Document­Frag­ment. W przykładzie przekazujemy zmodyfikowane drzewo Document. I tak właś­nie stworzyliśmy emulację tego, co uzyskaliśmy za pomocą JDOM-a — modyfikujemy i zwra­camy dokument XML z danymi wynikającymi z informacji podanych przez użytkownika.

Gdzie się podziała klasa XmlRpcConfiguration?

Kiedy rolę interfejsu użytkownika pełni serwlet lub inny mechanizm WWW, trzeba pamiętać o pewnych problemach, które klientów „statycznych” lub uproszczonych po prostu nie dotyczą. Jeden z nich to odśmiecanie i przestoje u użytkownika. Przestoje u użytkownika (ang. user lag) to sytuacja, w której użytkownik ładuje serwlet lub inny kod przez Internet, po czym robi sobie przer­wę na kawę, idzie na trzy zebrania i odwiedza bar kanapkowy. Odwołania do obiektów w serwlecie, do których uzyskano dostęp, mogą w tym czasie zostać poddane procedurom od­śmiecania. Kiedy jeden serwlet przekazuje żądania do drugiego, to problemu nie ma; jednak w naszym przykładzie serwlet XmlRpcConfigurationServlet przekazywał dane do same­go siebie. Może pojawić się błąd polegający na tym, że zmienna config (egzemplarz com.orei­l­ly.xml.XmlRpcConfiguration) zostanie tylko utworzona w metodzie doGet(), zaś po­nowne jej użycie nastąpi w doPost():

public void doGet(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

// Ładujemy informacje o konfiguracji za pomocą naszej klasy narzędziowej.

config = new XmlRpcConfiguration(CONFIG_FILENAME);

// Reszta metody.

}

public void doPost(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

// Zachowujemy nazwę hosta.

String hostname =

req.getParameterValues("hostname")[0];

if ((hostname != null) && (!hostname.equals(""))) {

config.setHostname(hostname);

}

// Reszta metody

}

Jeśli pomiędzy pierwszym żądaniem GET a następnymi żądaniami POST upłynęło wystarczająco dużo czasu, po próbie uzyskania dostępu do config z metody doPost() może zostać zgło­szony NullPointerException. Taka sytuacja może także nastąpić, jeśli w trakcie żądania kod serwleta zostanie zmieniony, a serwlet przeładowany (a pozwalają na to Jakarta Tomcat i inne popularne mechanizmy serwletów).

Aby uniknąć tego problemu, sensownie byłoby zagwarantować, że zmienna config jest popraw­na zarówno w metodzie doGet(), jak i doPost(). Jednak tworzenie egzemplarza klasy Xml­Rpc­Configuration wiąże się ze sporym nakładem pracy — konieczne jest ponowne prze­two­rzenie podanego pliku. Zamiast za każdym razem odtwarzać zmienną, można wykorzystać fakt, że zmienne poddawane operacjom odśmiecania przyjmują wartości równe null. Tak więc można sprawdzić obecność wartości null i stworzyć nowy egzemplarz jedynie w razie potrzeby:

public void doPost(HttpServletRequest req,

HttpServletResponse res)

throws ServletException, IOException {

// Aktualizujemy informacje o konfiguracji.

if (config == null) {

config = new XmlRpcConfiguration(CONFIG_FILENAME);

}

// Zachowujemy nazwę hosta.

String hostname =

req.getParameterValues("hostname")[0];

if ((hostname != null) && (!hostname.equals(""))) {

config.setHostname(hostname);

}

// Reszta metody.

}

Powyższe zmiany w kodzie gwarantują, że przestoje u użytkownika nie wpłyną na działanie programu.

Co dalej?

Najważniejsze zagadnienia związane z używaniem XML-a z poziomu programów w Javie zostały już omówione. Czytelnik potrafi tworzyć, przetwarzać, przekształcać zgodnie ze stylami oraz za­wę­żać dokumenty XML i sprawdzać ich poprawność; poznał też takie narzędzia jak struktury publikacji, XML z perspektywy zdalnego wywoływania procedur, przechowywanie konfiguracji w do­ku­mentach XML oraz sposób tworzenia danych XML „w locie”. W rozdziale 13. zostaną omówione aplikacje typu firma-firma. Zostanie przedstawione wykorzystanie XML-a w hipo­te­tycz­nych fir­mach i Czytelnik będzie mógł przyjrzeć się całemu procesowi komunikacji we­wnętrznej firmy oraz pomiędzy przedsiębiorstwami.

To stwierdzenie nie jest zupełnie ścisłe, jednakże w przypadku zmiennej nielokalnej jest prawdziwe, szczególnie w przy­padku serwletów. Więcej informacji o odśmiecaniu i wartościach zmiennych, których egzemplarzy nie utworzono lub któ­re poddano odśmiecaniu można znaleźć w książce Java in a Nutshell Davida Flangana (O'Reilly & Associates).

322 Rozdział 12. Tworzenie danych XML w języku Java

Praktyka 321

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

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

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



Wyszukiwarka

Podobne podstrony:
05-08, Programowanie, ! Java, Java i XML
02-08, Programowanie, ! Java, Java i XML
01-08, Programowanie, ! Java, Java i XML
14-08, Programowanie, ! Java, Java i XML
00-08-orig, Programowanie, ! Java, Java i XML
Java i XML, programowanie, Java
Java i XML, programowanie, Java
Java i XML, programowanie, Java
r12-05, Programowanie, ! Java, Java Server Programming
r20-05, Programowanie, ! Java, Java Server Programming
O Autorach-05, Programowanie, ! Java, Java Server Programming
programowanie C java
r05-05, Programowanie, ! Java, Java Server Programming
r07-05, Programowanie, ! Java, Java Server Programming
r03-05, Programowanie, ! Java, Java Server Programming
rE-05, Programowanie, ! Java, Java Server Programming
r19-05, Programowanie, ! Java, Java Server Programming
r17-05, Programowanie, ! Java, Java Server Programming

więcej podobnych podstron