Karol Gilarski
Jedną z części J2EE jest Java XML Pack. Składają się na niego następujące elementy:
Umożliwia pracę z danymi zapisanymi w XML'u za pomocą:
Umożliwia proste parsowanie dokumentów XML za pomocą parsera opartego na obsłudze zdarzeń. Parserowi podczepiamy napisaną przez nas klasę o odpowiednim interfejsie. Podczas parsowania parser będzie wywoływał opowiednie metody tej klasy.
Podstawowe zdarzenia, które można obsłużyć:
Podczepiając handler pod w/w zdarzenia mamy dostęp do wszystkich elementów dokumentu XML'a. Zaletą SAX'a jest prostota, natomiast wadą dostęp do danych w sposób sekwencyjny.
Przykład: (Uproszczone Echo - wychwytuje wszystkie tagi otwierające)
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
public class MyHandler extends DefaultHandler
{
public void startElement (
String namespaceURI,
String sName, // simple name
String qName, // qualified name
Attributes attrs)
throws SAXException
{
String eName = sName; // element name
if ("".equals(eName)) eName = qName; // not namespaceAware
emit("<"+eName);
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
String aName = attrs.getLocalName(i); // Attr name
if ("".equals(aName)) aName = attrs.getQName(i);
emit("");
emit(aName+"=\"+attrs.getvalue(i)+"\"");
}
}
emit(">");
}
private void emit(String s) {
// ... print string
}
}
|
Przykład:
try {
...
} catch (SAXParseException spe) {
// Blad wygenerowany przez parser
System.out.println("* Parsing error"
+ ", line " + spe.getLineNumber()
+ ", uri " + spe.getSystemId());
System.out.println(" " + spe.getMessage() );
}
|
import java.io.*; import javax.xml.parsers.*; ... String filename = "test.xml"; // nasz handler DefaultHandler handler = new MyHandler(); // pobranie fabryki parserow SAXParserFactory factory = SAXParserFactory.newInstance(); // parsowanie SAXParser saxParser = factory.newSAXParser(); saxParser.parse ( new File(filename), handler ); |
DOM jest to zbiór interfejsów, które odwzorowują strukturę dokumentu XML. Podstawowe interfejsy to Document, Element i Node. Obiekty tych interfejsów tworzą drzewo, które również nazywa się DOM'em.
Tak jak w przypadku SAX'a źródłem danych ogólnie jest strumień.
import org.w3c.dom.*;
import javax.xml.parsers.*;
...
String filename = "test.xml";
Document document;
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
try {
// tworzymy builder'a i zczytujemy dane
DocumentBuilder builder =
factory.newDocumentBuilder();
document = builder.parse ( new File(filename) );
}
catch (SAXParseException e) {
...
}
|
Jak się można domysleć odpowiada to tworzeniu drzewa. A więc zaczynamy od korzenia, a następnie doczepiamy kolejne kawałki, przy czym możemy to robić w każdej sensownej kolejności.
import org.w3c.dom.*; Document document; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { // polozenie fundamentow DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); } // budowa struktury Element root = (Element) document.createElement( "rootElement"); document.appendChild(root); root.appendChild( document.createTextNode("Some") ); root.appendChild( document.createTextNode(" ") ); root.appendChild( document.createTextNode("text") ); // taki dokument bedzie rownowazny tresciowo root.appendChild ( document.createTextNode ("Some text")); // .. natomiast calkowicie rownowazny // dopiero po normalizacji document.getDocumentElement().normalize(); } catch (ParserConfigurationException pce) { ... } |
Jak można zauważyć dane w węzłach odpowiadających tagom XML'a, przechowywane są jako odrębne węzły. Z tego powodu dokument XML'a może być równoważny kilku różnym DOM'om.
Poza tym mamy możliwość dochodzenia do konkretnego węzła za pomocą metody getElementsByTagName interfejsu Document. W efekcie dostajemy listę węzłów w kolejności odpowiadającej przechodzeniu dokumentu XML.
Niestety metoda ta ma ograniczenia, polegające na tym, że zawsze przeszukiwany jest cały dokument oraz jeśli szukamy konkretnego elementu, to musimy sobie ręcznie odfiltrować trafienia.
Ponadto DOM może być modyfikowany w dowolny sposób za pomocą metod:
Wszystkie te metody dotyczą jednak bardzo niskopoziomowych zmian, gdyż ograniczają się do manipulacji poszczególnych węzłów drzewa.
Przykład:
import javax.xml.transform.*; // zbudowanie transformera z arkuszu XSL TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer( new StreamSource("myXSL.xsl")); // Document doc = ... DOMSource source = new DOMSource(doc); // przygotowanie miejsca na wynik File newXML = new File("newXML.xml"); FileOutputStream os = new FileOutputStream(newXML); StreamResult result = new StreamResult(os); // uruchomienie transformacji transformer.transform(source, result); |
Dane wejściowe, tj. dane XML, mogą być pobierane zarówno ze strumienia (np. z pliku) jak i z wcześniej zbudowanej struktury DOM.
Przykład zastosowania XSLT:
Serwlet generuje wewnątrz siebie dokument XML, a następnie stosuje zadaną transformację z wykorzystaniem arkuszy XSL w celu uzyskania odpowiedniej prezentacji danych:
Kod przykładu (z pominięciem klas dostarczających danych)
Metoda ta jest bardzo podobna do RMI, z tym że klient może być napisany w dowolnym języku.
Dla servantu tworzy się interfejs identyczny co w przypadku RMI:
import java.rmi.Remote; import java.rmi.RemoteException; public interface CoffeeOrderIF extends Remote { public Coffee [] getPriceList() throws RemoteException; public String orderCoffee(String coffeeName, int quantity) throws RemoteException; } |
Następnie kodujemy servant implementujący wcześniej zdefiniowany interfejs:
public class CoffeeOrderImpl implements CoffeeOrderIF { public Coffee [] getPriceList() throws RemoteException; { . . . } public String orderCoffee(String coffeeName, int quantity) throws RemoteException; { . . . } } |
Podobnie jak w RMI mamy klasy tie po stronie serwera oraz klasy stub po stronie klienta (o ile klient jest napisany w Javie). Klasy te uzyskuje się poprzez wywołanie na uprzednio przygotowanym dokumencie WSDL narzędzia mapującego (xrpcc).
Dokument WSDL jest to dokument XML'a, który uzyskuje się poprzez wywołanie wspomnianego narzędzia na pliku ze zdefiniowanym interfejsem serwleta (w naszym przykładzie: CoffeeOrderIF). Zanim dokument WSDL będzie gotowy, należy go uzupełnić o takie informacje jak lokalizacja serwletu.
Servanty mogą być wystawiane na świat albo w postaci serwletów, albo jako EJB. W przypadku serwletów dokument WSDL będzie zawierał adres serwleta. W takim przypadku w momencie wywołania nie trzeba podawać lokalizacji serwletu.
CoffeeOrderIF coffeeOrder = new CoffeeOrderServiceImpl().getCoffeeOrderIF(); |
Można również wywołać xrpcc bezpośrednio na interfejsie, w tym przypadku klient musi sam podać lokalizację serlwletu:
HelloIF_Stub stub = (HelloIF_Stub) (new HelloWorld_Impl().getHelloIF()); stub._setProperty( javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/jaxrpc-dynamic/jaxrpc/HelloIF"); System.out.println(stub.sayHello("Duke!")); |
Klasy HelloIF_Stub oraz HelloWorld_Impl są to klasy wygenerowane przez program xrpcc.
Sam proces instalacji serwletu jest bardziej skomplikowany, niż ma to normalnie miejsce. Program xrpcc tworzy również plik konfiguracyjny, do którego odwołanie trzeba zamieścić w pliku web.xml opisującym serwlet dla serwera aplikacji.
... <servlet> ... <init-param> <param-name>configuration.file</param-name> <param-value> /WEB-INF/HelloWorld_Config.properties </param-value> </init-param> ... </servlet> ... |
Wiadomość w formacie SOAP ma ogólnie następującą postać:
I. SOAP message A. SOAP part 1. SOAP envelope a. SOAP header b. SOAP body B. Attachment part 1 C. Attachment part 2 ... |
Zarówno nagłówek jak i załączniki są opcjonalne.
Przesyłanie wiadomości może odbywać się na dwa sposoby:
Jest to prostsze rozwiązanie. Klient tworzy połączenie bezpośrednio do odbiorcy. Przesyłanie wiadomości jest sychroniczne, tj. klient oczekuje od razu odpowiedzi.
Przykład:
// tworzenie polaczenia SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance(); SOAPConnection con = factory.createConnection(); // tworzenie wiadomosci MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage m = messageFactory.createMessage(); // dostanie sie do tresci wiadomosci ... SOAPPart sp = m.getSOAPPart(); SOAPEnvelope envelope = sp.getSOAPEnvelope(); SOAPBody body = envelope.getSOAPBody(); // stworzenie tresci SOAPBodyElement bodyElement = body.addBodyElement( envelope.createName("text", "hotitems", "http://hotitems.com/products/gizmo"); bodyElement.addTextNode("some-xml-text"); // wyslanie wiadomosci pod wskazany adres SOAPMessage response = soapConnection.call(message, urlEndpoint); |
Jako treść wiadomości można też przesłać przygotowany wcześniej DOM, bądź też podać StreamSource jako źródło danych XML'owych.
Document doc = ... // uprzednio przygotowany DOM DOMSource domSource = new DOMSource(doc); soapPart.setContent(domSource); |
Do wiadomości można podłączyć załączniki, np. pliki:
SOAPMessage m = ... URL url = new URL("http://foo.bar/img.jpg"); DataHandler dh = new DataHandler(url); AttachmentPart attachPart = m.createAttachmentPart(dh); m.addAttachmentPart(attachPart); |
... bądź też dowolny strumień danych:
InputStream is = ... AttachmentPart attachPart = m.createAttachmentPart(); attachPart.setContent (is); m.addAttachmentPart(attachPart); |
W przypadku zastosowania dostawcy wiadomości wysyłanie wiadomości jest asynchroniczne, tzn. że jest nieblokujące oraz najpierw wiadomość jest przesyłana do dostawcy. Dopiero ten przesyła ją do adresata.
Dostawca działa w sposób ciągły, tzn. może przyjmować wiadomości dla klienta, nawet jeśli ten nie jest podłączony do dostawcy. W takiej sytuacji dostawca przechowa wiadomość i prześle ją dopiero w momencie, kiedy klient będzie dostępny.
Użycie dostawcy jest warunkiem koniecznym do odbierania wiadomości. Wiążę się z tym jednak konieczność uruchamiania klienta w kontenerze, np. serwera aplikacji.
Przykład użycia dostawcy:
ProviderConnectionFactory pcFactory =
ProviderConnectionFactory.newInstance();
ProviderConnection pcCon =
pcFactory.createConnection();
// stworzenie wiadomosci
SOAPMessage msg = ...
pcCon.send(msg);
|
Adresat przesyłanej wiadomości ustalany jest na podstawie sekcji header elementu SOAPEnvelope.
Umożliwia rejestrację oraz odszukiwanie różnych serwisów sieciowych. Stosuje sie m.in. w interakcjach B2B (Business to Business).
Architektura JAXR składa się z :
Przykład:
import javax.xml.registry.*; ConnectionFactory connFactory = ConnectionFactory.newInstance(); // ustawienie namiarow na przykładowy rejestr Properties props = new Properties(); props.setProperty("javax.xml.registry.queryManagerURL", "http://www-3.ibm.com/services/uddi/v2beta/inquiryapi"); props.setProperty("javax.xml.registry.lifeCycleManagerURL", "https://www-3.ibm.com/services/uddi/v2beta/protect/publishapi"); props.setProperty("javax.xml.registry.factoryClass", "com.sun.xml.registry.uddi.ConnectionFactoryImpl"); // stworzenie polaczenia connFactory.setProperties(props); Connection connection = connFactory.createConnection(); // pobranie odpowiednich interfejsow RegistryService rs = connection.getRegistryService(); BusinessQueryManager qm = rs.getBusinessQueryManager(); BusinessLifeCycleManager lcm = rs.getBusinessLifeCycleManageger(); |
QueryManager umożliwia zadawanie pytań rejestrowi.
LifeCycleManager pozwala na modyfikację rejestru.
// stworzenie organizacji Organization org = lcm.createOrganization("The Coffee Break"); org.setDescription( "Purveyor of only the finest coffees. Established 1895"); // sklasyfikowanie organizacji ClassificationScheme cScheme = bqm.findClassificationSchemeByName("ntis-gov:naics"); Classification classification = (Classification)lcm.createClassification(cScheme, "Snack and Nonalcoholic Beverage Bars", "722213"); Collection classifications = new ArrayList(); classifications.add(classification); org.addClassifications(classifications); // rejestracja Collection orgs = new ArrayList(); orgs.add(org); lcm.saveOrganizations(orgs); |
Aby rejestr zmodyfikować należy się z reguły uwierzytelnić. Wykonuje się to poprzez metodę setCredentials obiekty Connection:
String username = ... String password = ... PasswordAuthentication passwdAuth = new PasswordAuthentication( username, password.toCharArray()); Set creds = new HashSet(); creds.add(passwdAuth); connection.setCredentials(creds); |
// zdefiniowanie kryteriow wyszukiwania Collection findQualifiers = new ArrayList(); findQualifiers.add(FindQualifier.CASE_SENSITIVE_MATCH), Collection namePatterns = new ArrayList(), namePatterns.add("%Coffee%"); // wyszukanie organizacji w oparciu // o nazwę oraz zdefiniowaną klasyfikację BulkResponse response = bqm.findOrganizations(findQualifiers, namePatterns, null, classifications, null, null); Collection orgs = response.getCollection(); |
JAXR obsługuje również zapytania SQL:
DeclarativeQueryManager dqm = rs.getDeclarativeQueryManager(); Query query = dqm.createQuery(Query.QUERY_TYPE_SQL, "SELECT id FROM RegistryEntry WHERE name LIKE %Coffee% " + "AND majorVersion >= 1 AND " + "(majorVersion >= 2 OR minorVersion >= 3)"); BulkResponse response2 = dqm.executeQuery(query); |
W celu zapewnienia współpracy z klientami innymi niż napisanymi w Javie, komunikacja pomiędzy klientem JAXR a rejestrem oparta jest na JAXM.