• +49-(0)721-402485-12
Ihre Experten für XML, XQuery und XML-Datenbanken

Web Services

Im letzten Kapitel wurde SOAP als Stand-alone-Technologie vorgestellt; die entwickelte Applikation bestand aus einem SOAP-Client, der mit einem SOAP-Server kommunizierte, und das Ganze basierte lediglich auf Java und einer Servlet-Engine. Dies ist eine akzeptable Lösung für ein System, in dem alle Clients und Dienste (Services) selbst entwickelt werden. Leider wird die Interoperabilität mit anderen Lösungen auf diese Weise stark eingegrenzt. Wenn man etwas über SOAP liest oder hört, so ist es gerade dieser Aspekt der Interoperabilität, der für eine gute Presse sorgte. Das letzte Kapitel war nicht komplett. Es erklärte nicht, wie Sie es schaffen, anderen Applikationen Dienste anzubieten, die SOAP als Transportmechanismus benutzen. Dieses Kapitel wird die restlichen Bestandteile liefern und die verbliebenen Probleme bezüglich der Interoperabilität lösen.

Ich werde zunächst einige Definitionen angeben, die Ihnen helfen sollen, den etwas überstrapazierten Ausdruck »Web Services« besser einzuordnen. Obwohl es schwer ist, diese Formulierung jetzt schon genau einzuordnen, da jeder diesen Begriff für seine eigene Ausprägung von Software oder Architektur verwendet, sind einige Prinzipien doch immer gleich. In fast jeder Definition von Web Services werden Sie die Aussage finden, daß ein gewisser Bedarf an Informationsaustausch zwischen Applikationen besteht. Dieser Austausch benötigt eine Menge an Standards, und die zwei wichtigsten (zumindest im Moment) sind UDDI und WSDL. Ich werde hier zeigen, wie beide zum Themenkreis SOAP passen. Ich werde dieses Kapitel damit abschließen, all die verschiedenen Abkürzungen und Technologien in einem Beispiel zu vereinen.

Web Services

Web Services scheinen das nächste »Große Ding« in der Informationstechnologie zu werden, daher wird ihnen in dieser Auflage ein eigenes Kapitel gewidmet. Dennoch ist es schwer, dafür eine Definition zu finden. Aus der Perspektive eines einzelnen mag eine spezielle Definition korrekt sein, während alle anderen sagen: »Thema verfehlt!« Daher wird hier versucht, aus dem Hype und den Unterschieden verschiedener Implementierungen die Menge an Konzepten herauszudestillieren, die auf alle Varianten zutreffen.

Bei Web Services dreht sich alles um Interoperabilität. Applikationen müssen mehr und mehr miteinander kommunizieren. Jedoch werden die Probleme bei der Kommunikation mit jedem Tag des technologischen Fortschritts immer zahlreicher, anstatt abzunehmen. Mit der Entwicklung neuer Sprachen, immer komplizierterer Datenstrukturen und sich ändernden Geschäftsbedürfnissen wird der Unterschied zwischen verschiedenen Applikationen (auch, wenn sie demselben Zweck dienen!) immer größer. Damit eine Interoperabilität möglich wird, müssen die Systeme eine gemeinsame Sprache sprechen. Dies ist keine Sprache im Sinne von Java; Programme, die in Java geschrieben wurden, müssen genauso Zugriff haben wie solche, die in anderen Sprachen entwickelt wurden. Statt dessen ist damit eine Sprache gemeint, auf die jeder zugreifen kann, der über ein entsprechendes Wörterbuch verfügt. Auch wenn die Wörter sich unterscheiden, kann man diese doch in etwas übersetzen, was man versteht.

XML löst dieses Problem der Datenübertragung als eine Art von Sprache. Es ist in fast jeder Programmiersprache akzeptiert und verwendbar: C, Perl, Python, LISP, Java, Pascal, C#, Visual Basic... Die Liste läßt sich beliebig fortsetzen. Web-Service-Frameworks versuchen, noch einen Schritt weiterzugehen. Der Schlüssel zu Interoperabilität liegt nicht nur in den Daten, sondern in der Bedeutung der Daten. Was für Informationen kann ich von dem anderen System bekommen? Was kann ein anderer von mir erfahren? Mit anderen Worten: Es müssen Möglichkeiten vorhanden sein, die von einer Applikation angebotenen Dienste bekanntzumachen. Und genau da klaffte bis vor kurzem eine Lücke.

Neuere Entwicklungen auf dem Gebiet der Web Services haben begonnen, diese Lücke zu füllen. Erstens erlaubt es UDDI, andere Dienste aufzuspüren und eigene Dienste so zu registrieren, daß andere sie finden können. Zweitens bietet WSDL eine Möglichkeit, Informationen über einen gefundenen Dienst zur Verfügung zu stellen, die es einem Client gestatten, damit zu interagieren. Zunächst soll die Erklärung, wofür UDDI und WSDL stehen, noch ausgespart bleiben, damit die Konzentration auf das große Ganze nicht verlorengeht.

Abbildung 13-1 zeigt die Abläufe während dieses Prozesses. Zu Beginn entwickelt ein Provider einen Service (wie das im letzten Kapitel geschah). Dieser Service könnte so etwas einfaches wie einen CD-Katalog darstellen oder etwas so kompliziertes wie die Speicherung von KFZ-Kennzeichen, die von der Landesregierung Thüringens benutzt werden. Kann auf diese Services über das Web zugegriffen werden, sind sie Kandidaten für eine Service-Registry. Dies alles ist Teil von UDDI. UDDI gibt dem Benutzer auch die Möglichkeit, alle registrierten Services nach einem spezifischen Namen, wie zum Beispiel »cd-catalog« oder »reg-kfz-kennz.« zu durchsuchen. Die Registry liefert dann alle passenden Services.

Mit etwas Glück hat der Client den gewünschten Service gefunden. Für eine Interaktion benötigt er jedoch mehr als den Namen: Die URL, zu der verbunden werden soll, die zu übergebenden Argumente und die Bedeutung des Rückgabewertes sind ebenfalls nötig. Dies wird durch XML im WSDL-Format erreicht, was noch erklärt werden wird. Dann kann der Client mit dem gefundenen Service interagieren, im Wissen (wegen WSDL), daß er korrekt benutzt wird. Das Leben ist wundervoll, und das nur wegen der Web Services. Natürlich sind bisher die Schwierigkeiten unerwähnt geblieben, aber mit denen befassen wir uns nun nach der Übersicht über das »große Ganze«.

Abbildung 13-1: Der Web-Services-Prozeß
Der Web-Services-Prozeß

UDDI

Ohne weitere Verzögerung werde ich nun definieren, was UDDI eigentlich ist. Es steht für Universal Discovery, Description, and Integration (Universelle Entdeckung, Beschreibung und Integration) und wird oft mit dem Wort Registry umschrieben. Der beste Ort, um etwas über UDDI zu lernen, ist die Website http://uddi.xml.org/ (siehe Abbildung 13-2), die auch die UDDI-Registry beherbergt, die eine so wichtige Rolle bei der Registrierung und dem Auffinden von Services spielt. Diese Site beschreibt das UDDI-Projekt, das versucht, ein solches komplettes Framework für den Austausch von Daten zu definieren, wie es in diesem Kapitel schon skizziert wurde. Diese Initiative wird von IBM und Microsoft unterstützt, so daß man davon ausgehen kann, daß es auch langfristig verfügbar ist.

Abbildung 13-2: Die Website von UDDI
Die Website von UDDI

Der Kern ist ein Netzwerk von Diensten, über die UDDI Informationen gespeichert hat. Die Möglichkeit, neue Dienste zu registrieren und nach existierenden zu suchen, ist ein Teil der Registry, der online über die Website verfügbar ist. Alles, was man dazu braucht, ist eine einfache Registrierung. Weder IBM noch Microsoft verlangen von einzelnen oder Unternehmen, hohe Gebühren zu zahlen oder ihre eigenen Services öffentlich zugänglich zu machen. Daher werden Tag für Tag mehr Services registriert, als das in einem profitorientierten System der Fall wäre.

Das ist alles, was es zu UDDI zu sagen gibt. Die Komplexität von UDDI liegt nicht in der Benutzung von UDDI, sondern in der Implementierung, über die Sie sich aber als Provider oder Nutzer von Services keine Gedanken machen müssen. Es existieren viele UDDI-Implementierungen, die heruntergeladen werden können und die lokal laufen. Ich bevorzuge jUDDI. Sie können dieses Java-basierte Open Source-Projekt unter http://www.juddi.org finden. Außerdem enthält das Web Services Toolkit von IBM (das im Abschnitt »WSDL« in diesem Kapitel näher beleuchtet wird) eine Testversion einer privaten UDDI-Registry. Auf beide Implementierungen wird hier nicht näher eingegangen, da das nicht dem Verständnis von UDDI, sondern lediglich dem der Implementierung dienen würde.

Wenn Sie daran interessiert sind, was bei UDDI unter der Haube steckt, sollten Sie sich jUDDI ansehen. Falls Sie lediglich Web Services entwickeln und anderen zur Verfügung stellen möchten, brauchen Sie sich darüber keine Gedanken machen. Die Registrierung und die Suche nach spezifischen Diensten sollen den Abschluß dieses Kapitels bilden, in dem ein recht komplexes Beispiel unter Benutzung von SOAP, UDDI und WSDL präsentiert wird.

WSDL

WSDL ist die Web Services Description Language (Web-Services-Beschreibungssprache). Die vollständige Spezifikation ist online unter http://www.w3.org/TR/wsdl verfügbar und beschreibt alles, was Sie über einen Service wissen müssen, wenn Sie mit ihm interagieren möchten. Wie auch UDDI ist es eine recht einfache Technologie (genau betrachtet, ist es gar keine Technologie, sondern nur Markup), aber ein sehr wichtiger Teil im großen Ganzen der Web Services. Eine WSDL-Datei beschreibt einige wichtige Informationen, über die ein Client zur Nutzung eines Services verfügen muß:

  • den Namen des Services, einschließlich URN
  • den Ort, an dem auf den Service zugegriffen werden kann (normalerweise eine HTTP-URL-Adresse)
  • die aufrufbaren Methoden
  • die Argumenttypen und die Typen der Rückgabewerte für jede Methode

Jede dieser Informationen für sich genommen ist nutzlos, zusammengenommen repräsentieren sie aber die komplette Sicht eines Clients auf einen Service. Zusätzlich enthält eine solche Datei noch Elemente aus XML Schema, Parameter im Style von XML-RPC und eine ziemliche Menge von vielen Dingen, die in diesem Buch bereits behandelt wurden. Beispiel 13-1 zeigt einen Teil des WSDL-Schemas für den CD-Katalog aus dem letzten Kapitel, es beschreibt die Methode getCD( ) des Services. Es ist nicht vollständig, sollte aber einen Eindruck vom Aussehen eines WSDL-Dokuments liefern.

Beispiel 13-1: Teil eines WSDL-Dokuments

<?xml version="1.0"?>

<definitions name="CDCatalog"
             targetNamespace="http://www.oreilly.com/javaxml2/cd-catalog.wsdl"
             xmlns:cd="http://www.oreilly.com/javaxml2/cd-catalog.wsdl"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:cdXSD="http://www.oreilly.com/javaxml2/cd-catalog.xsd"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
>
  <types>
    <schema targetNamespace="http://www.oreilly.com/javaxml2/cd-catalog.xsd"
            xmlns="http://www.w3.org/2000/10/XMLSchema">
      <element name="Title">
        <complexType>
          <all><element name="title" type="string" /></all>
        </complexType>
      </element>
      <element name="CD">
        <complexType>
          <all>
            <element name="title" type="string" />
            <element name="artist" type="string" />
            <element name="label" type="string" />
          </all>
        </complexType>
      </element>
    </schema>
  </types>

  <message name="getCDInput">
    <part name="body" element="cdXSD:Title" />
  </message>

  <message name="getCDOutput">
    <part name="body" element="cdXSD:CD" />
  </message>

  <portType name="CDCatalogPortType">
    <operation name="getCD">
      <input message="cd:getCDInput" />
      <output message="cd:getCDOutput" />
    </operation>
  </portType>

  <binding name="CDCatalogBinding" type="cd:CDCatalogPortType">
    <soap:binding style="rpc" 
                  transport="http://schemas.xmlsoap.org/soap/http" />
    <operation name="getCD">
      <soap:operation soapAction="urn:cd-catalog" />
      <input>
        <soap:body use="encoded"
            encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
            namespace="urn:cd-catalog" />
      </input>
      <output>
        <soap:body use="encoded"
            encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
            namespace="urn:cd-catalog" />
      </output>
    </operation>
  </binding>

  <service name="CDCatalog">
    <documentation>CD Catalog Service from Java and XML</documentation>
    <port name="CDCatalogPort" binding="cd:CDCatalogBinding">
      <soap:address location="http://newInstance.com/soap/servlet/rpcrouter" />
    </port>
  </service>
</defintions>

Wie Sie sehen, ist dieses Format zur Beschreibung eines Services recht ausschweifend, immerhin ist es auch einfach zu verstehen. Zunächst werden alle Typen, die über das Netzwerk gesendet werden sollen, durch das Element types und eine an XML Schema angelehnte Syntax beschrieben.

Zur Zeit berücksichtigt WSDL nur die 2000-er XML Schema-Spezifikation und nicht die finalisierte Version von April 2001. Das bedeutet, daß Sie zur Zeit noch die älteren Schema-Konstrukte benutzen müssen, solange WSDL in dieser Beziehung nicht auf dem aktuellsten Stand ist.

Als nächstes wird das message-Element benutzt, um die Interaktion von Client zu Server und von Server zu Client zu definieren. Diese sind im portType-Element kombiniert, um verfügbare Operationen zu definieren (Sie finden noch weitere verfügbare Operationen in diesem Abschnitt). Das binding-Element erklärt, wie auf die Methoden zugegriffen werden kann, und gibt die URN an, unter der auf sie zugegriffen werden kann, das service-Element schließlich gruppiert all diese. Sich das alles als eine Art Prozeß vorzustellen hilft, dabei die Übersicht zu behalten.

Im Moment nutzt die Apache-SOAP-Implementierung WSDL nicht direkt. Es ist also zum Beispiel nicht möglich, automatisch eine Client-Klasse aus einem WSDL-Dokument zu generieren. Während einige andere Anbieter, wie zum Beispiel Microsoft, hier bereits weiter sind, arbeitet das Apache Axis-Projekt noch an dieser Funktionalität. Momentan müssen Sie ein WSDL-Dokument noch selbst interpretieren und den Code für den Client von Hand stricken. Das macht sowieso mehr Spaß.

Alle Teile zusammenfügen

Mit diesem eher grundlegenden Verständnis von WSDL im Zusammenhang mit der UDDI-Diskussion sind Sie in der Lage, ein komplettes Web-Services-Beispiel zu erstellen. In diesem Abschnitt werde ich zeigen, wie ein SOAP-Service erstellt wird (diesmal ein auf Messaging beruhender), wie er bei einer UDDI-Registry registriert und mittels UDDI gefunden wird und wie ein Client schließlich nach dem Holen des WSDL-Deskriptors darauf zugreift.

Für das Beispiel wird noch etwas Komplexität hinzugefügt. Hier ist das Szenario: CDs-R-Us soll ein neues Unternehmen sein, das Händler weltweit mit CDs beliefern möchte. Da dieses Unternehmen recht spät am Markt erscheint, versucht es, Marktanteile durch ein High-Tech-Interface zu erringen, auf das mittels Web Services zugegriffen werden kann, um die Interaktion zu erleichtern. CDs-R-Us will die Möglichkeit schaffen, mittels XML-Botschaften (Messages) CDs über SOAP anzufordern. Die Applikationen auf Unternehmensseite werden diese Forderungen erfüllen, indem sie die entsprechenden CDs im Katalog-Server suchen (eine erwachsene Variante des im letzten Kapitel vorgestellten CDCatalog-Service) und anschließend eine Rechnung zurückliefern. Hier finden zwei SOAP-Interaktionen statt: eine vom Client zu CDs-R-Us, die Messaging-basiert ist, und eine innerhalb von CDs-R-Us, die RPC-basiert ist. Abbildung 13-3 zeigt den Ablauf des kompletten Prozesses. Das Unternehmen möchte den Service außerdem bei einer UDDI-Registry registrieren, damit potentielle Kunden ihn finden können.

Abbildung 13-3: Prozeßablauf der Beispielanwendung
Prozeßablauf der Beispielanwendung

Ein Messaging-Service

Da der CDCatalog aus dem letzten Kapitel für den RPC-Client genutzt werden soll, können Sie gleich zu dem neuen Code des Messaging-Service übergehen. Dieser soll eine XML-Kauforder empfangen und eine Anfrage an den Katalog-Service auf einem anderen Rechner im Unternehmensnetzwerk von CDs-R-Us richten. Damit ist der Messaging-Service gleichzeitig ein SOAP-RPC-Client. Das ist in der Welt der Web Services absolut zulässig und kommt auch sehr oft vor. Ein Dienst empfängt Informationen von einem anderen und initiiert daraufhin eine Interaktion mit einem dritten. Wenn Ihnen das immer noch komisch erscheint, fragen Sie mal jemanden, der sich ein Haus baut, wie viele Firmen er anstellt, und diese wiederum, wie viele Unterauftragnehmer sie haben; das sprengt wahrscheinlich Ihr Vorstellungsvermögen!

Zuerst muß das Format der Kauforder (KO) definiert werden, welches CDs-R-Us fordert. Das XML Schema für das KO-Dokument ist in Beispiel 13-2 zu sehen.

Beispiel 13-2: Das XML Schema ko.xsd

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

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns="http://www.cds-r-us.com"
           targetNamespace="http://www.cds-r-us.com">
  <xs:element name="purchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="recipient" />
        <xs:element ref="order" />
      </xs:sequence>
      <xs:attribute name="orderDate" type="xs:string" />
    </xs:complexType>
  </xs:element>

  <xs:element name="recipient">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="name" />
        <xs:element ref="street" />
        <xs:element ref="city" />
        <xs:element ref="state" />
        <xs:element ref="postalCode" />
      </xs:sequence>
      <xs:attribute name="country" type="xs:string" />
    </xs:complexType>
  </xs:element>

  <xs:element name="name" type="xs:string"/>
  <xs:element name="street" type="xs:string" />
  <xs:element name="city" type="xs:string" />
  <xs:element name="state" type="xs:string" />
  <xs:element name="postalCode" type="xs:short" />

  <xs:element name="order">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="cd" maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="cd">
    <xs:complexType>
      <xs:attribute name="artist" type="xs:string" />
      <xs:attribute name="title" type="xs:string" />
    </xs:complexType>
  </xs:element>
</xs:schema>

Unter Benutzung dieses Schemas würde eine typische KO wie in Beispiel 13-3 gezeigt aussehen.

Beispiel 13-3: Beispiel-KO für CDs

<purchaseOrder orderDate="07.23.2001" 
    xmlns="http://www.cds-r-us.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.cds-r-us.com po.xsd"
>
  <recipient country="USA">
    <name>Dennis Scannell</name>
    <street>175 Perry Lea Side Road</street>
    <city>Waterbury</city>
    <state>VT</state>
    <postalCode>05676</postalCode>
  </recipient>
  <order>
    <cd artist="Brooks Williams" title="Little Lion" />
    <cd artist="David Wilcox" title="What You Whispered" />
  </order>
</purchaseOrder>

Der Service sollte ein XML-Dokument, wie es in Beispiel 13-3 gezeigt wird, akzeptieren, herausfinden, welche Informationen relevant sind, und diese Informationen dann via RPC an den CD-Katalog-Service weiterleiten. Nach Erhalt der Antwort formuliert er eine Art von Rechnung oder Bestätigung für den Messaging-Client und sendet diese Botschaft zurück. Das Beispiel ist bewußt einfach gehalten, Sie werden jedoch leicht sehen, wo Sie weitere Verarbeitungsschritte hinzufügen können, wenn Sie den Code betrachten.

Das Schreiben eines Service, der XML akzeptiert, ist ein bißchen schwieriger als das Schreiben eines Service, der RPC-Requests akzeptiert; beim Messaging müssen Sie direkter mit den Request- und Antwort-Objekten interagieren, außerdem muß die Klasse über SOAP Bescheid wissen. Zur Erinnerung: Bei der Verarbeitung via RPC mußte die Klasse weder etwas über RPC noch über SOAP wissen und war daher recht gut gekapselt. Bei einem auf Messaging beruhenden Service müssen alle Methoden, mit denen interagiert werden soll, folgender Konvention entsprechen:

public void methodName(SOAPEnvelope env, SOAPContext req, SOAPContext res)
    throws java.io.IOException, javax.mail.MessagingException;

Das ähnelt ein wenig der Arbeitsweise von Servlets. Sie bekommen ein Request- und ein Antwort-Objekt, mit denen interagiert wird, wie auch den eigentlichen SOAP-Envelope für die über das Netzwerk verschickte Botschaft. Sie sehen die erwartete IOException, die ausgelöst werden könnte, wenn Netzwerk- oder andere Probleme auftreten; zusätzlich kann es zu einer MessagingException (aus dem JavaMail-Package) kommen, wenn Probleme mit dem SOAP-Envelope auftreten. Außerdem muß der Name der Methode mit dem root-Element des Botschaftsinhalts übereinstimmen!1 Das wird leicht vergessen; in unserem Fall muß der Name der den XML-Code empfangenden Methode purchaseOrder lauten, da dies der Name des root-Elements in Beispiel 13-3 ist.

Mit diesem Wissen ist es möglich, das Skelett eines Messaging-Service aufzusetzen. Dieses Skelett ist in Beispiel 13-4 gezeigt; zusätzlich zum integrierten Framework zum Empfang von SOAP-Botschaften wurde die Logik zur Durchführung geeigneter Aufrufe des CDCatalog-Service auf einem anderen Rechner integriert. Ein Kommentar dient als Platzhalter für den Messaging-Code, mit dem es gleich weitergeht.

Beispiel 13-4: Skelett für den Messaging-Service für CDs-R-Us

package javaxml2;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.mail.MessagingException;

// SOAP-Importe:
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.rpc.SOAPContext;
import org.apache.soap.util.xml.QName;

public class OrderProcessor {

    /** Mapping für die Klasse CD */
    private SOAPMappingRegistry registry;

    /** Der Serializer für die Klasse CD */
    private BeanSerializer serializer;

    /** Das RPC-Aufruf-Objekt */
    private Call call;

    /** Parameter für den Aufruf */
    private Vector params;

    /** Antwort vom RPC-Aufruf */
    private Response rpcResponse;

    /** Die URL, zu der eine Verbindung hergestellt werden soll */
    private URL rpcServerURL;         

    public void initialize(  ) {
        // Interne URL für SOAP-RPC
        try {
            rpcServerURL = 
                new URL("http://localhost:8080/soap/servlet/rpcrouter");
        } catch (MalformedURLException neverHappens) {
            // ignoriert
        }

        // SOAP-Mapping zum Konvertieren von CD-Objekten
        registry = new SOAPMappingRegistry(  );
        serializer = new BeanSerializer(  );
        registry.mapTypes(Constants.NS_URI_SOAP_ENC,
            new QName("urn:cd-catalog-demo", "cd"),
            CD.class, serializer, serializer); 

        // Erzeugen eines Aufruf-Objekts zum internen SOAP-Service
        call = new Call(  );        
        call.setSOAPMappingRegistry(registry);
        call.setTargetObjectURI("urn:cd-catalog");
        call.setMethodName("getCD");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

        // Input definieren
        params = new Vector(  );
    }

    public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
        throws IOException, MessagingException {

        // SOAP-Environment initialisieren
        initialize(  );

        // Liste der erfolgreich bestellten CDs
        List orderedCDs = new LinkedList(  );

        // HashTable für die fehlgeschlagenen Bestellungen
        Hashtable failedCDs = new Hashtable(  );

        // Parsen der Message und Erstellen einer Liste zu bestellender CDs

        // Schleife über alle CDs aus dem KO-Request
            String artist = "";
            String title = "";

            // Parameter initialisieren
            params.clear(  );
            params.addElement(new Parameter("title", String.class, title, null));
            call.setParams(params);

            try {  
                // Aufrufen
                rpcResponse = call.invoke(rpcServerURL, "");

                if (!rpcResponse.generatedFault(  )) {
                    Parameter returnValue = rpcResponse.getReturnValue(  );
                    CD cd = (CD)returnValue.getValue(  );

                    // CD verfügbar?
                    if (cd == null) {
                        failedCDs.put(title, "Gewünschte CD ist nicht erhältlich.");
                        continue;
                    }
 
                    // Überprüfen, ob sie vom gewünschten Künstler ist
                    if (cd.getArtist(  ).equalsIgnoreCase(artist)) {
                        // CD zur Liste mit den erfolgreichen Bestellungen hinzufügen
                        orderedCDs.add(cd); 
                    } else {
                        // Zu fehlgeschlagenen Bestellungen hinzufügen
                        failedCDs.put(title, "Falscher Künstler für die angegebene CD.");
                    }
                } else {
                    Fault fault = rpcResponse.getFault(  );
                    failedCDs.put(title, fault.getFaultString(  ));
                }
            } catch (SOAPException e) {
                failedCDs.put(title, "SOAP Exception: " + e.getMessage(  ));
            }

        // Am Ende der Schleife etwas Sinnvolles an den Client zurückgeben
    }
}

In diesem Beispiel und dem Rest des Kapitels wird der Hostname http://localhost:8080 benutzt, um einen SOAP-Service auf dem lokalen Rechner zu repräsentieren. In den meisten Fällen werden Sie das Beispiel lokal testen, und dies verhindert, daß Sie sich fiktive Hostnamen ausdenken müssen und frustriert sind, wenn die Beispiele nicht funktionieren.

In einer realen Umgebung würde der Client zu einem Rechner von CDs-R-Us Verbindung aufnehmen, wie zum Beispiel http://www.cds-r-us.com. Der Messaging-Service würde dann einen Rechner wie http://catalog.cds-r-us.com kontaktieren, auf dem der CD-Katalog (vielleicht hinter einer Firewall) läuft. Bei Beispielen sollte man jedoch mehr Wert darauf legen, daß sie funktionieren, daher wird hier überall der lokale Rechner als Hostname benutzt.

Ich werde nun zunächst beschreiben, was eigentlich abläuft, danach kommen wir zu den interessanten Stücken. Zunächst wird ein RPC-Aufruf für jeden Client mittels der Methode initialize( ) kreiert. Dieses Call-Objekt wird immer wieder verwendet, damit die Ressourcen, die ein einzelner Client in Anspruch nimmt, möglichst gering gehalten werden. Gleichzeitig erhält jeder Client sein eigenes Call-Objekt, damit keine Synchronisations- oder Threading-Probleme auftauchen. Danach wird ein wenig Speicher organisiert: eine List für die erfolgreichen Bestellungen und eine Hashtable für die fehl-geschlagenen. Die Hashtable hat den Titel der CD als Schlüssel und das aufgetretene Problem als Wert. Anschließend wird die SOAP-Botschaft gelesen (hier steht erst einmal lediglich ein Kommentar). Daraufhin beginnt eine Schleife, die für jede bestellte CD den Titel und den Künstler aus der Botschaft extrahiert und einen RPC-Aufruf absetzt, mit dem das gewünschte CD-Objekt geholt werden soll. Abhängig vom Resultat des Requests wird die CD an die Liste der erfolgreichen Bestellungen angehängt oder in die Hashtable der fehlgeschlagenen Bestellungen eingefügt. Am Ende der Schleife wird eine Botschaft konstruiert und an den Client zurückgeschickt.

Sie sollten bedenken, daß der CDCatalog eine einfache Version und nicht komplett ist. Ein realer CD-Service würde wahrscheinlich noch prüfen, ob die CD im Warenbestand enthalten und eine entsprechende Anzahl an Exemplaren verfügbar ist und so weiter. In dem Beispiel prüft der Katalog-Service lediglich, ob die geforderte CD in der Liste der verfügbaren CDs enthalten ist oder nicht. Trotzdem wird das Prinzip klar.

Nachdem das Skelett nun steht, sind Sie in der Lage, mit der Botschaft des Benutzers zu interagieren. Dazu werden noch einige Klassen mehr benötigt. Die im folgenden gezeigten import-Anweisungen müssen nun hinzugefügt werden:

import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import javax.mail.MessagingException;

// DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

// SOAP-Importe:
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.rpc.SOAPContext;
import org.apache.soap.util.xml.QName;

Der Code benötigt DOM, um mit dem XML-Code in der vom Nutzer gesendeten Botschaft klarzukommen; diese Botschaft wird hier als erstes betrachtet. Beispiel 13-3 zeigt XML-Code, der den Inhalt der Botschaft darstellt, den der Service zu empfangen erwartet. Die Botschaft wird jedoch in einige SOAP-Spezifika verpackt und letztlich so aussehen, wie in Beispiel 13-5 dargestellt. Die zusätzlichen Informationen werden von SOAP benutzt, um die Botschaft zu interpretieren.

Beispiel 13-5: Das SOAP-fähige Dokument aus Beispiel 13-3

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:Body>
  <purchaseOrder orderDate="07.23.2001" 
      xmlns="urn:cd-order-service"
  >
    <recipient country="USA">
      <name>Dennis Scannell</name>
      <street>175 Perry Lea Side Road</street>
      <city>Waterbury</city>
      <state>VT</state>
      <postalCode>05676</postalCode>
    </recipient>
    <order>
      <cd artist="Brooks Williams" title="Little Lion" />
      <cd artist="David Wilcox" title="What You Whispered" />
    </order>
  </purchaseOrder>
 </s:Body>
</s:Envelope>

Die eigentliche Botschaft ist im Body des SOAP-Envelope untergebracht. Diese Strukturen werden in Apache SOAP durch org.apache.soap.Envelope und org.apache.soap.Body dargestellt. Um auf die Einträge im Body zugreifen zu können, benutzen Sie envelope. getBody().getBodyEntries( ), das einen Vector zurückliefert. Das erste (und einzige) Element in diesem Vector ist im Beispiel ein DOM-Element, das das Java-Äquivalent des XML-purchaseOrder-Elements darstellt. Das ist genau das, was beabsichtigt war. Wenn Sie dieses Element extrahiert haben, können Sie mit normalen DOM-Methoden weiterarbeiten, um den gesamten DOM-Baum zu durchlaufen und jede bestellte CD abzuarbeiten. Der folgende Code muß noch zur Methode purchaseOrder( ) hinzugefügt werden. Er iteriert über die vom Client angeforderten CDs und extrahiert deren Informationen:

    public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
        throws IOException, MessagingException {

        // Initialisieren des SOAP-Environments
        initialize(  );

        // Instanz für die erfolgreich bestellten CDs
        List orderedCDs = new LinkedList(  );

        // HashTable für die fehlgeschlagenen Bestellungen 
        Hashtable failedCDs = new Hashtable(  );

        // Holen des Elements purchaseOrder – immer der erste Eintrag im Body
        Vector bodyEntries = env.getBody().getBodyEntries(  );
        Element purchaseOrder = (Element)bodyEntries.iterator().next(  );

        // In einer realen Anwendung würde jetzt irgend etwas mit der 
        // Käuferinformation geschehen
 
        // Holen der bestellten CDs
        Element order = 
            (Element)purchaseOrder.getElementsByTagName("order").item(0);
        NodeList cds = order.getElementsByTagName("cd");

        // Schleife über alle bestellten CDs des KO-Requests
        for (int i=0, len=cds.getLength(  ); i<len; i++) {
            Element cdElement = (Element)cds.item(i);
            String artist = cdElement.getAttribute("artist");
            String title = cdElement.getAttribute("title");

            // Einrichten des SOAP-RPC-Aufrufs wie in Beispiel 13-4
            params.clear(  );
            params.addElement(new Parameter("title", String.class, title, null));
            call.setParams(params);

            try {  
                // Existierender RPC-Code aus Beispiel 13-4 
            } catch (SOAPException e) {
                failedCDs.put(title, "SOAP Exception: " + e.getMessage(  ));
            }
        }

        // Am Ende der Schleife sollte irgend etwas Sinnvolles an den Client 
        // zurückgegeben werden
    }

Wenn dieser Code fertig ausgeführt wurde, enthält die Liste orderedCDs eine Liste aller bestellten CDs, deren Bestellung erfolgreich war, und die HashTable failedCDs enthält alle, deren Bestellung fehlschlug. Da der Client bereits weiß, wie man XML »spricht« (er hat ja eine XML-Botschaft geschickt), erscheint es sinnvoll, ihm eine XML-Antwort zu senden. Anstatt diese von Hand zu generieren, für SOAP zu formatieren und dann zu antworten, können Sie das Envelope-Objekt benutzen, aus dem gerade gelesen wurde. Dazu müssen Sie den folgenden Code einfügen, der die Antwort generiert:

    public void purchaseOrder(Envelope env, SOAPContext req, SOAPContext res)
        throws IOException, MessagingException {

        // Weiter oben bereits gezeigter Code zum Parsen beim Messaging

        // Schleife über alle bestellten CDs aus dem KO-Request
        for (int i=0, len=cds.getLength(  ); i<len; i++) {
            Element cdElement = (Element)cds.item(i);
            String artist = cdElement.getAttribute("artist");
            String title = cdElement.getAttribute("title");

            // Input definieren
            params.clear(  );
            params.addElement(new Parameter("title", String.class, title, null));
            call.setParams(params);

            try {  
                // Existierender RPC-Code aus Beispiel 13-4
            } catch (SOAPException e) {
                failedCDs.put(title, "SOAP Exception: " + e.getMessage(  ));
            }
        }

        // Am Ende der Schleife sollte irgend etwas Sinnvolles an den Client 
        // zurückgegeben werden
        Document doc = new org.apache.xerces.dom.DocumentImpl(  );
        Element response = doc.createElement("response");
        Element orderedCDsElement = doc.createElement("orderedCDs");
        Element failedCDsElement = doc.createElement("failedCDs");
        response.appendChild(orderedCDsElement);
        response.appendChild(failedCDsElement);

        // Hinzufügen der sortierten CDs
        for (Iterator i = orderedCDs.iterator(); i.hasNext(  ); ) {
            CD orderedCD = (CD)i.next(  );
            Element cdElement = doc.createElement("orderedCD");
            cdElement.setAttribute("title", orderedCD.getTitle(  ));
            cdElement.setAttribute("artist", orderedCD.getArtist(  ));
            cdElement.setAttribute("label", orderedCD.getLabel(  ));
            orderedCDsElement.appendChild(cdElement);
        }

        // Hinzufügen der fehlgeschlagenen CDs
        Enumeration keys = failedCDs.keys(  );
        while (keys.hasMoreElements(  )) {
            String title = (String)keys.nextElement(  );
            String error = (String)failedCDs.get(title);
            Element failedElement = doc.createElement("failedCD");
            failedElement.setAttribute("title", title);
            failedElement.appendChild(doc.createTextNode(error));
            failedCDsElement.appendChild(failedElement);
        }

        // Spezifizieren des Inhalts für den Envelope
        bodyEntries.clear(  );
        bodyEntries.add(response);
        StringWriter writer = new StringWriter(  );
        env.marshall(writer, null);

        // Senden des Envelopes zurück an den Client
        res.setRootPart(writer.toString(  ), "text/xml");
    }

Das erstellt einen XML-Baum, der die erfolgreichen und fehlgeschlagenen Bestellungen enthält. Danach wird dieser Baum als Inhalt des Bodys des Envelopes gesetzt, der damit die originale Anfrage des Clients ersetzt. Anschließend wird der Envelope in ein Textformat übersetzt, das benötigt wird, um die Antwort über das res-Objekt der Klasse SOAPContext zu verschicken. SOAP stellt die Mittel dazu durch die Methode marshall( ) zur Verfügung. Wenn Sie dieser Methode einen StringWriter übergeben, können Sie die »hineingeschriebenen« Wörter später als String extrahieren. Das zweite Argument ist eine Instanz der Klasse org.apache.soap.util.xml.XMLJavaMappingRegistry. Ein Beispiel ist die Klasse SOAPMappingRegistry, eine von der früher und im letzten Kapitel benutzten XMLJavaMappingRegistry abgeleitete Klasse. Da keine speziellen Typen benötigt werden, genügt hier null als Argument.

Schließlich wird das Resultat dieser ganzen Arbeit und Serialisierung als Inhalt der Antwort durch das Element setRootPart( ) eingefügt. Das zweite Argument dieser Methode ist der HTML-Content-Type. Da der Code XML zurückschickt, ist »text/xml« der korrekte Wert. Wenn der Client diese Antwort bekommt, kann er ermitteln, daß der Inhalt in XML vorliegt, und sie entsprechend decodieren. Um zu antworten, müssen Sie nur den Inhalt des SOAPContext-Antwort-Objekts korrekt setzen. Wenn diese Methode abgearbeitet ist, wird der SOAP-Server das Objekt mit dem Inhalt, der hineingeschrieben wurde, automatisch zum Client zurückschicken. Das ähnelt ebenfalls der Arbeit mit dem HttpServletResponse-Objekt in Servlets, wenn Sie mit dieser Methodik vertraut sind.

Nun kann die Klasse OrderProcessor kompiliert und auf dem SOAP-Server installiert werden:

java org.apache.soap.server.ServiceManagerClient 
    http://localhost:8080/soap/servlet/rpcrouter deploy xml/OrderProcessorDD.xml

Nachdem Sie das getan haben, können Sie den Service bei einer UDDI-Registry registrieren.

Registrieren bei UDDI

Um den Prozeß der Registrierung zu beginnen, müssen Sie zunächst sicherstellen, daß der Dienst öffentlich zugänglich ist. Sie können keinen Dienst registrieren, der nur lokal zugänglich ist (http://localhost:8080 und dergleichen). In der Test- oder Experimentalphase sollten Sie diesen Abschnitt einfach nur lesen und sich später daran erinnern. Wenn Sie aber bereit sind, einen Service tatsächlich in einem Netzwerk zu registrieren, sollten Sie sicherstellen, daß der Hostname der Maschine, auf der er läuft, bekannt ist. Dann sollten Sie mal bei http://uddi.xml.org/ vorbeisurfen.

Auf der UDDI-Website müssen Sie zunächst den Link »Register« oben rechts anklicken (siehe Abbildung 13-2, wenn das nicht klar ist). Danach wählen Sie den Knoten, in dem Sie Ihren Service registrieren wollen. Im Moment ist es noch so, daß nach der Registrierung in einem bestimmten Knoten der Dienst trotzdem in allen zugreifbar ist, daher ist das eine ziemlich unwichtige Entscheidung. Sie können zum Beispiel IBM wählen und anschließend die Schaltfläche »GO« anklicken. Hier müssen Sie über einen Zugang verfügen, um auf die IBM-Registry zugreifen zu dürfen. Falls Sie keinen haben, ist es einfach und kostenlos, einen zu bekommen. Sie klicken dafür auf die Schaltfläche »Register« links und folgen dann den Instruktionen. Haben Sie einen Zugang, melden Sie sich zunächst mittels des Links »Login« auf der linken Seite an (siehe Abbildung 13-4). Sie müssen hier beim ersten Login Ihren Aktivierungsschlüssel parat haben, den Sie nach der Registrierung per E-Mail bekommen.

Abbildung 13-4: Einloggen in die IBM-UDDI-Registry
Einloggen in die IBM-UDDI-Registry

Wenn Sie die Registrierungshürde genommen und Ihren Zugang aktiviert haben, können Sie Ihren Service publizieren. Zunächst müssen Sie auswählen, ob Sie ein neues »Business« eröffnen möchten. Das ist generell keine schlechte Idee. Ein Beispiel für ein neu angelegtes Business ist in Abbildung 13-5 zu sehen.

Abbildung 13-5: Hinzufügen eines neuen Business zu der UDDI-Registry
Hinzufügen eines neuen Business zu der UDDI-Registry

Sie können nun Services hinzufügen und dem jeweiligen Business zuordnen, was der Suche eine weitere Organisationsebene hinzufügt. Optional können Geschäftskontakte, -orte und anderes hinzugefügt werden. Ist das alles abgeschlossen, können Sie nun den Service zur Registry hinzufügen.

Dazu wählen Sie »Add a new service« und geben den Namen des Services an; für das Beispiel könnte das etwa cd-order-service sein. Man kann außerdem eine Beschreibung des Services, den Access-Point und die Service-Location für den Service angeben – also zum Beispiel: »Dieser Dienst erlaubt es, CDs mittels einer Kauforder zu bestellen«, »http« und »newInstance.com/soap/servlet/rpcrouter« als Beschreibung, Access-Point bzw. Service-Location. Ersetzen Sie hier die jeweils zutreffenden Daten für Hostname und URL. Anschließend wird ein Service-Locator spezifiziert. Das ist ein formaler Satz an Standards, der zur Kategorisierung des Service dient; das wird hier nicht näher erklärt. Lesen Sie dazu bitte auf der Website nach. Wenn alle Informationen eingegeben sind, sollte auf dem Bildschirm etwas ähnliches wie in Abbildung 13-6 zu sehen sein.

Abbildung 13-6: Ergebnisse nach dem Hinzufügen eines Service
Ergebnisse nach dem Hinzufügen eines Service

Hier beginnen die Dinge nun, ein wenig ineffizienter zu werden. Unglücklicherweise existiert keine Möglichkeit, ein den Service beschreibendes WSDL-Dokument auf den Server hochzuladen: Das würde es erlauben, seine technisch formulierte Beschreibung zusammen mit dem eigentlichen Dienst denen zur Verfügung zu stellen, die als Client mit dem Service interagieren möchten. Die einzigen erlaubten Informationen sind der Name des Services und der Access-Point (oder die Access-Points, wenn mehrere existieren). Das macht es aber schon für jeden mit einer UDDI-Registrierung und einem Login möglich, nach einem Service mit dem zugeordneten »Business« zu suchen. Damit ist der Service registriert und für alle verfügbar.

Suchen in der UDDI-Registry

Die andere Seite der Medaille ist die Suche nach einem Service. Wir schlüpfen aus der Rolle des Service-Providers in die des Clients, des Konsumenten von Diensten oder Services. Wollen Sie SOAP, WSDL und den ganzen Rest benutzen, besuchen Sie die UDDI-Website, melden Sie sich an und suchen Sie nach Services (wie zum Beispiel dem, der weiter oben registriert wurde). Das ist einfach: Sie klicken einfach den Link »Find« an, selektieren den Knoten, in dem Sie suchen möchten (wieder »IBM«), und geben den Namen ein, nach dem gesucht werden soll. Dazu müssen Sie sich anmelden, wenn Sie das nicht schon getan haben.

Dies ist ein weiteres Gebiet, auf dem sich die Web Services erst noch entwickeln; die Suche ist noch ziemlich einfach gestrickt und kann nur basierend auf dem Namen des Services durchgeführt werden. Existiert ein Service mit dem Namen »Bibliotheks-Ausleih-Service« und Sie geben als Suchtext »Buch« ein, werden Sie diesen Service nicht finden. Dazu müßten Sie »Ausleih« oder »Bibliotheks« eingeben. Es ist aber wenigstens ein Anfang. Um den gerade registrierten Service zu finden, können Sie »cd« als Suchtext benutzen. Sie sollten angeben, daß Sie Services und nicht »Businesses« oder Service-Typen suchen möchten. Nun können Sie die Schaltfläche »Find« auf der Suchseite anklicken. Das sollte Resultate ähnlich denen in Abbildung 13-7 liefern (die den von mir registrierten CD-Service enthalten und möglicherweise andere von Lesern dieses Buches, nachdem es veröffentlicht wurde).

Abbildung 13-7: Resultate für die Suche nach »cd«
Resultate für die Suche nach cd

Wenn Sie nun auf den Service-Namen klicken, können Sie den früher angegebenen Access-Point wie auch die anderen Informationen sehen, die schon eingegeben wurden (wie zum Beispiel, ob ein Service-Typ oder eine Kategorie gewählt wurde). Auch hier könnten Sie annehmen, daß eine WSDL-Beschreibung herunterladbar ist, dies ist jedoch zur Zeit nicht möglich. Momentan müßten Sie dann denjenigen kontaktieren, der den Service zur Verfügung stellt, und die verfügbaren Methoden erfragen sowie entsprechende, eventuell kostenpflichtige Arrangements ausarbeiten, um den Service zu benutzen.

Die UDDI-Registries haben noch einige Arbeit vor sich; die Infrastruktur für eine vielseitige Registrierung von und die Suche nach Services liegt allerdings vor. Wird es zusätzlich noch möglich, WSDL-Beschreibungen hoch- (für Service-Provider) und herunterzuladen (für Benutzer von Services), wird die persönliche Interaktion zur Nutzung von Services nicht mehr nötig sein. Obwohl LCD-Bildschirme keinen menschlichen Kontakt und Geborgenheit bieten können, wird das zur verstärkten Nutzung der Dienste und dadurch wiederum zur verstärkten Entwicklung von Diensten sorgen.

WSDL

Dieser Abschnitt wird sich um die Nützlichkeit von WSDL-Dokumenten drehen. Hier wird gezeigt, wie Sie mittels eines solchen Dokuments und eines einfachen Tools, wie zum Beispiel dem schon früher in diesem Kapitel erwähnten WSTK von IBM, einen Java-Client erzeugen können. Ich würde gerne erklären, wie WSDL Leben retten kann – jetzt, heute! Das ist zwar noch nicht ganz der Fall, statt dessen wird hier gezeigt, was sich um Java und WSDL entwickelt, damit Sie gewappnet sind, wenn alle Einzelteile den ihnen zustehenden Platz eingenommen haben werden.

Es ist anzunehmen, daß eine Reihe von Werkzeugen auftauchen werden, die es erlauben, automatisch WSDL für einen Java-Service, wie für die Klassen OrderProcessor oder CDCatalog zum Beispiel, zu erzeugen. In der Tat sind einige solche Werkzeuge bereits verfügbar. Das WSTK von IBM wurde bereits erwähnt, dazu kommen andere Pakete, wie von The Mind Electric (Glue, unter http://www.themindelectric.com), Silverstream (http://www.silverstream.com) und SOAPWiz. Ich habe sie mit wechselndem Erfolg eingesetzt.

In einfachen Fällen wie dem CDCatalog konnten alle Pakete WSDL erzeugen (obwohl sich das Toolkit von IBM an der Methode, die eine Hashtable zurückliefert, verschluckt hat). Das funktioniert deshalb so gut, weil die Methoden ziemlich primitive Java-Typen wie String und CD als Argumente erwarten und als Rückgabewert liefern; CD besteht seinerseits aus Fundamentaltypen.

Die Probleme fangen an, wenn man versucht, diese Tools auf die Klasse OrderProcessor anzuwenden. Da diese Klasse Message- statt RPC-basiert ist, erwartet sie mit Envelope und SOAPContext komplexe Typen als Argumente. Da diese wiederum aus komplexen Typen zusammengesetzt sind, reagieren WSDL-Generatoren ziemlich schnell mit Verwirrung, verschlucken sich schließlich und spucken am Ende Stacktraces aus. Die Tools haben noch ziemliche Arbeit vor sich, bevor sie mit SOAP-Services oder komplexen RPC-basierten Services umgehen können.

Das Fazit ist zweischneidig: Einerseits ist die Perspektive für die Zukunft ziemlich aufregend. Wenn Werkzeuge verfügbar werden, die diese komplexen Typen handhaben können, können Sie WSDL sogar für komplexe SOAP-Services einfach erzeugen. Dann können solche Werkzeuge sogar Clients generieren, die sich mit diesen Services unterhalten. Stellen Sie sich die Suche in einer UDDI-Registry nach einem solchen Service, gefolgt vom Herunterladen des WSDL-Deskriptors und das wiederum gefolgt von der Generierung des entsprechenden Clients für diesen Dienst vor. Nach nur minimalen, für die jeweilige Anwendung spezifischen Code-Änderungen können Sie bereits mit dem Service arbeiten. Die Zukunft sieht gut aus für Web Services (wie auch für Bücher über dieses Thema, könnte man sich vorstellen).

Die andere Seite ist, daß man zur Zeit immer noch das Telefon bemühen muß, um mit jemandem über den Service zu sprechen, zumindest in absehbarer Zukunft. Sobald jemand die Signaturen der Methoden verrät, mit denen man interagieren kann oder sie per E-Mail verschickt (Programmierer sind manchmal ein wenig scheu in der direkten Kommunikation), sind Sie in der Lage, einen Client zu programmieren, was nun beschrieben wird.

Programmierung eines Clients

Um einen Client zu schreiben, müssen Sie den Service, auf den Sie zugreifen möchten, und die Methoden, die Sie benutzen möchten, kennen und wissen, welche Botschaften Sie senden dürfen. Beispiel 13-6 zeigt einen solchen Client, den Sie nur noch kompilieren müssen und anschließend benutzen können.

Beispiel 13-6: Der Client CDOrderer

package javaxml2;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;

// SAX- und DOM-Importe
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

// SOAP-Importe
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.SOAPException;
import org.apache.soap.messaging.Message;
import org.apache.soap.transport.SOAPTransport;
import org.apache.soap.util.xml.XMLParserUtils;

public class CDOrderer {

    public void order(URL serviceURL, String msgFilename) 
        throws IOException, SAXException, SOAPException {

        // Parsen der XML-Message
        FileReader reader = new FileReader(msgFilename);
        DocumentBuilder builder = XMLParserUtils.getXMLDocBuilder(  );
        Document doc = builder.parse(new InputSource(reader));
        if (doc == null) {
            throw new SOAPException(Constants.FAULT_CODE_CLIENT, 
                "Fehler beim Parsen der XML-Nachricht.");
        }

        // Erzeugen des Envelopes
        Envelope msgEnvelope = Envelope.unmarshall(doc.getDocumentElement(  ));

        // Senden der Message
        Message msg = new Message(  );
        msg.send(serviceURL, "urn:cd-order-service", msgEnvelope);

        // Verarbeitung der Antwort
        SOAPTransport transport = msg.getSOAPTransport(  );
        BufferedReader resReader = transport.receive(  );

        String line;
        while ((line = resReader.readLine(  )) != null) {
            System.out.println(line);
        }
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Benutzung: java javaxml2.CDOrderer " +
                "[XML-Nachrichten-Dateiname]");
            return;
        }

        try {
            URL serviceURL = 
                new URL("http://localhost:8080/soap/servlet/messagerouter");

            CDOrderer orderer = new CDOrderer(  );
            orderer.order(serviceURL, args[0]);
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Dies ist ein sehr einfacher Client. Er nimmt eine XML-Nachricht von der Kommandozeile und konvertiert sie in einen SOAP-Envelope. Die Instanz der Klasse org.apache.soap.messaging.Message kapselt diesen dann und sendet ihn zu der Service-URN unter dem angegebenen Host-Namen. Die Antwort erhält er über den SOAPTransport der Message und gibt sie einfach auf den Bildschirm aus. In Ihren eigenen Anwendungen könnten Sie sie in eine XML-Datei schreiben, extrahieren und mittels DOM oder JDOM manipulieren und dann mit der Verarbeitung fortfahren.

Anstatt direkt eine Instanz der Klasse org.apache.xerces.dom.DocumentImpl zu erzeugen, wurden hier die JAXP-Klasse DocumentBuilder und die XMLParserUtils-Klasse von SOAP benutzt, um produktspezifischen Code zu vermeiden. Das ist eine bessere Technik als die in der Klasse OrderProcessor gezeigte, wo die Xerces-Klasse direkt referenziert wird. Ich zeige hier beide Vorgehensweisen, damit Sie einen guten Eindruck vom Unterschied bekommen; ich empfehle Ihnen, den Code der Klasse OrderProcessor so zu ändern, daß er das Verhalten des hier vorgestellten Clients nachahmt.

Wenn alle benötigten SOAP-Klassen im Klassenpfad stehen und die Klasse CDOrderer kompiliert ist, können Sie das Ganze ausprobieren. Dazu muß sichergestellt werden, daß die Services an den URNs urn:cd-order-service und urn:cd-catalog verfügbar sind. Zusätzlich könnten Sie eine oder zwei der CDs im Dokument ko.xml aus Beispiel 13-2 und Beispiel 13-3 hinzufügen. Ich schlage vor, es zunächst mit einer CD im Katalog zu versuchen, damit Sie eine erfolgreiche und eine fehlgeschlagene Bestellung sehen, und anschließend beide in den Katalog aufzunehmen, damit beide erfolgreich sind:

C:\javaxml2\build>java javaxml2.CDAdder 
    http://localhost:8080/soap/servlet/rpcrouter 
    "Little Lion" "Brooks Williams" "Signature Sounds"
CD hinzufügen 'Little Lion' von 'Brooks Williams', auf dem Label 
    Signature Sounds
CD erfolgreich hinzugefügt.

Vergewissern Sie sich, daß Sie das SOAP-kompatible XML aus Beispiel 13-5 gespeichert haben; vielleicht unter koMsg.xml im xml-Verzeichnis. Schließlich können Sie den Client starten:

bmclaugh@GANDALF
$ java javaxml2.CDOrderer c:\javaxml2\ch13\xml\poMsg.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<response>
 <orderedCDs>
  <orderedCD artist="Brooks Williams" label="Signature Sounds" 
             title="Little Lion"/>
 </orderedCDs>
 <failedCDs>
  <failedCD title="What You Whispered">Angeforderte CD ist nicht verfügbar.</failedCD>
 </failedCDs>
</response>
</s:Body>
</s:Envelope>

Das Programm spuckt die XML-Antwort des Servers aus; Ich habe die Antwort noch formatiert, um sie lesbarer zu gestalten. Sie sollten beim Test eine ähnliche Ausgabe sehen (in diesem Beispiel befand sich die Brooks-Williams-CD im Katalog, nicht jedoch die von David Wilcox, wie weiter oben gezeigt).

An diesem Punkt sollten Sie sich mit dem Schreiben von SOAP-Servern und -Clients ziemlich vertraut fühlen. Zusätzlich werden Sie wahrscheinlich erkennen, daß UDDI und WSDL gar nicht so kompliziert sind. Mit SOAP zusammen benutzt, stellen sie ein passables Framework für Web Services und die Interbusiness-Kommunikation dar. Es empfiehlt sich, einen Blick auf die fortgeschritteneren Features von Apache-SOAP oder der SOAP-Implementierung zu werfen, die Sie eben gerade benutzen. Apache-SOAP zum Beispiel unterstützt SMTP (Simple Mail Transport Protocol) als Transportmedium für RPC und Messaging. Das wird hier nicht behandelt, da es ein sehr fortgeschrittenes Feature von SOAP ist und die Spezifikation SMTP als Transportmedium noch nicht abdeckt. Mit anderen Worten: Es ist implementierungsabhängig. Ich vermeide das, wann immer es möglich ist. Die Möglichkeiten der benutzten SOAP-Implementierung zu kennen erhöht die Effektivität eigener Web Services auf jeden Fall.

Was mache ich jetzt?

Zu diesem Zeitpunkt sind Sie wahrscheinlich so weit, drei oder vier Kapitel mehr von diesem Zeug zu vertragen. Ein Kapitel über das Arbeiten mit UDDI-Registries von Programmen aus, eins über die Arbeit mit WSDL, einige Beispiele mehr ... das wäre doch spaßig. Natürlich würde das ein Buch über Web Services ergeben und nicht eins über Java und XML. Es gibt jedoch einige Ressourcen online, mit deren Hilfe Sie den nächsten Level erreichen können. Eine gute Adresse ist die Site von IBM unter http://www.ibm.com/developerworks/webservices; für die Arbeit mit Microsoft-Clients (C#, Visual Basic und COM-Objekte) sollten Sie http://msdn.microsoft.com/en-us/library/ms995800.aspx besuchen. Mit anderen Worten: Benutzen Sie dieses Kapitel als soliden Absprungspunkt. Besuche der hier angegebenen Websites und das Ausschauhalten nach weiteren Büchern von O’Reilly zum Thema SOAP kann ich nur empfehlen.

Und was kommt jetzt?

Sie kennen sich nun mit Web Services aus und sind bereit, die Welt zu erobern, richtig? Na gut, nicht ganz. Wie mit allem anderen in Java gibt es so viele Wege, Business-to-Business- und Service-basierte Kommunikation zu betreiben, wie Leute, die sich damit beschäftigen. Sie sollten nicht annehmen, daß der neueste Hype (SOAP, UDDI und WSDL) die endgültige, alles könnende Lösung ist, daher wird im nächsten Kapitel eine völlig andere Art der Lösung von Interkommunikationsproblemen beschrieben, die verschiedene Sprachen, Servlets und die RSS (Rich Site Summary) benutzt. Das sollte Ihre Kenntnisse wunderbar abrunden.

1)
Dies ist eine Anforderung der Apache SOAP-Implementierung, nicht von der SOAP-Spezifikation. Da es sich aber quasi zum Standard entwickelt hat und außerdem eine gute Programmiertechnik ist, benutze ich es auch hier.