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

SOAP

SOAP heißt Simple Object Access Protocol (engl. für: einfaches Objektzugriffsprotokoll). Wenn Sie bisher noch nichts davon gehört haben, haben Sie wahrscheinlich einsam in einer Höhle gelebt. Es ist der neueste Trend in der Web-Programmierung und integraler Bestandteil des Web-Services-Fanatismus, der die neueste Generation der Web-Entwicklung auszeichnet. Wenn Sie von Microsofts .NET oder der Peer-to-Peer-»Revolution« gehört haben, dann haben Sie von Technologien gehört, die auf SOAP basieren (auch wenn Sie das nicht wissen). Es gibt nicht nur eine, sondern zwei SOAP-Implementierungen im Apache-Umfeld, und Microsoft hat diesem Thema Hunderte Seiten auf der MSDN-Website gewidmet (http://msdn.microsoft.com).

In diesem Kapitel erkläre ich, was SOAP eigentlich ist und warum es ein so wichtiger Teil dessen ist, wohin sich die Web-Entwicklung bewegt. Das wird helfen, die Grundlagen von SOAP zu verstehen und Sie darauf vorbereiten, mit einem SOAP-Toolkit zu arbeiten. Von dort aus werde ich verfügbare SOAP-Projekte kurz vorstellen und dann in die Apache-Implementierung eintauchen. Dieses Kapitel soll SOAP nicht im Detail beleuchten, das nächste Kapitel füllt einen Großteil der Lücken. Sie sollten das vorliegende Kapitel als ersten Teil einer Mini-Serie betrachten – viele der bis zum Ende dieses Kapitels aufgetauchten Fragen werden im nächsten Kapitel Web Services beantwortet.

Start

Zunächst müssen Sie einmal verstehen, was SOAP ist. Sie können dazu die komplette W3C-Note-Submission unter http://www.w3.org/TR/SOAP lesen, die aber ziemlich lang ist. Wenn man all den Hype beiseite läßt, ist SOAP nur ein Protokoll. Es ist ein einfaches Protokoll (einfach zu benutzen, nicht notwendigerweise einfach zu schreiben). Es basiert auf der Idee, daß an irgendeinem Punkt in einer verteilten Architektur die Notwendigkeit besteht, Informationen auszutauschen. Zusätzlich ist das Protokoll auf einem überlasteten Server, auf dem viele Prozesse laufen, leichtgewichtig und hat einen minimalen Overhead. Schließlich erlaubt es, all das über HTTP auszuführen. Das wiederum macht es möglich, Schwierigkeiten, wie zum Beispiel Firewalls, zu umgehen. Außerdem entfällt die Notwendigkeit, alle möglichen Sockets an komischen Portnummern lauschen zu lassen. Haben Sie das erstmal verinnerlicht, ist der Rest nur noch eine Menge unbedeutender Details.

Natürlich interessieren Sie sich vor allem für die Details, also werde ich sie nicht auslassen. Es gibt drei grundlegende Komponenten in der SOAP-Spezifikation: den SOAP-Envelope (engl. für: Umschlag, Kuvert), eine Menge von Codierungsregeln und ein Mittel zur Kommunikation zwischen Request und Antwort. Stellen Sie sich eine SOAP-Botschaft als eine Art Brief vor; Sie wissen schon – diese antiquierten Dinger in einem Kuvert mit einer Briefmarke und einer vorn drauf gekritzelten Adresse. Diese Analogie macht es wesentlich einfacher, solche SOAP-Konzepte zu verstehen. Abbildung 12-1 versucht, den SOAP-Prozeß mittels dieser Analogie zu verdeutlichen.

Abbildung 12-1: Der SOAP-Message-Prozeß
Der SOAP-Message-Prozeß

Mit diesem Bild im Hinterkopf schauen wir nun auf die drei Komponenten der SOAP-Spezifikation. Ich werde zunächst kurz auf jede eingehen und anschließend Beispiele aufzeigen, die jede Komponente deutlicher illustrieren. Es sind diese drei Komponenten, die SOAP so wichtig und wertvoll machen. Die Fehlerbehandlung, die Unterstützung verschiedenster Codierungen, die Serialisierung benutzerdefinierter Parameter und die Tatsache, daß SOAP über HTTP läuft, macht es in vielen Szenarien attraktiver als andere verteilte Protokolle.1 Zusätzlich ermöglicht SOAP einen hohen Grad an Interoperabilität mit anderen Applikationen, worauf ich umfassender im nächsten Kapitel eingehen werde. Im Moment konzentriere ich mich auf die Grundlagen von SOAP.

Der Envelope

Der SOAP-Envelope ist ein Analogon zum Umschlag oder Kuvert eines normalen Briefes. Er enthält Informationen über die Botschaft, die in der SOAP-Nutzlast codiert ist, einschließlich der Daten über Sender und Empfänger sowie auch Details über die Botschaft selbst. Zum Beispiel kann der Header einer SOAP-Botschaft vollständige Angaben darüber enthalten, wie die Botschaft zu verarbeiten ist. Bevor eine Anwendung mit der Verarbeitung einer Botschaft beginnt, kann sie zunächst Informationen über die Botschaft extrahieren. Sie kann daran sogar erkennen, ob sie überhaupt in der Lage ist, die Botschaft zu verarbeiten.

Anders als bei XML-RPC (Sie erinnern sich? XML-RPC-Botschaften, die Codierung und der Rest waren alle Bestandteil eines einzigen XML-Fragments) erfolgt mit SOAP eine wirkliche Interpretation der Botschaft, um etwas über sie herauszubekommen. Eine typische SOAP-Botschaft kann außerdem den Encoding-Style beinhalten, der den Empfänger bei der Interpretation der Botschaft unterstützt. Beispiel 12-1 zeigt den SOAP-Envelope mit der angegebenen Codierung.

Beispiel 12-1: Der SOAP-Envelope

<soap:Envelope 
   xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
   soap:encodingStyle="http://myHost.com/encodings/secureEncoding"
>
  <soap:Body>
    <article xmlns="http://www.ibm.com/developer">
      <name>Soapbox</name>
      <url>
        http://www-106.ibm.com/developerworks/library/x-soapbx1.html
      </url>
    </article>
  </soap:Body>
</soap:Envelope>

Sie können sehen, daß die Codierung im Envelope angegeben wird. Das erlaubt es der Anwendung zu entscheiden (sie benutzt dazu den Wert des Attributs encodingStyle), ob sie die ankommende Botschaft im Body-Element benutzen kann. Sie sollten darauf achten, den SOAP-Namensraum korrekt zu benutzen, da ansonsten SOAP-Server, die die Botschaft empfangen, Fehler wegen nicht übereinstimmender Versionen melden würden und damit keine Interaktion mit diesen möglich wäre.

Codierung

Das zweite wichtige Element, das SOAP auf den Tisch packt, ist ein einfaches Mittel zum Codieren von benutzerdefinierten Datentypen. In RPC (und XML-RPC) kann lediglich eine vordefinierte Menge von Datentypen codiert werden, nämlich die, die von dem jeweils benutzten XML-RPC-Toolkit unterstützt werden. Die Codierung anderer Datentypen würde es erfordern, den XML-RPC-Server und die Clients zu modifizieren. Mit SOAP dagegen können XML Schemas benutzt werden, um einfach neue Datentypen zu definieren (unter Benutzung der Struktur complexType, die weiter vorn im Kapitel Einstieg in XML erläutert wurde). Diese neuen Typen können leicht innerhalb der SOAP-Nutzlast in XML repräsentiert werden. Wegen dieser Integration von XML Schema kann jeder Datentyp in einer SOAP-Botschaft codiert werden, den man logisch in einem XML Schema beschreiben kann.

Aufruf

Sie werden am schnellsten verstehen, wie ein SOAP-Aufruf funktioniert, wenn Sie ihn mit etwas vergleichen, das Sie bereits kennen, wie zum Beispiel XML-RPC. Wie Sie sich erinnern werden, sieht ein XML-RPC-Aufruf ungefähr so wie in dem in Beispiel 12-2 gezeigten Code-Fragment aus.

Beispiel 12-2: Aufruf mittels XML-RPC

// Angabe des zu benutzenden XML-Parsers
XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

// Angabe des Servers, zu dem verbunden werden soll
XmlRpcClient client =
    new XmlRpcClient("http://rpc.middleearth.com");

// Erzeugen der Parameter
Vector params = new Vector(  );
params.addElement(flightNumber);
params.addElement(numSeats);
params.addElement(creditCardType);
params.addElement(creditCardNum);

// Anforderung einer Reservierung
Boolean boughtTickets = 
    (Boolean)client.execute("ticketCounter.buyTickets", params);

// Arbeiten mit der Antwort

Ich habe hier eine einfache Anwendung programmiert, die ein Ticketverkaufsbüro darstellt. Schauen Sie sich nun Beispiel 12-3 an, das den Aufruf mittels SOAP zeigt.

Beispiel 12-3: Aufruf in SOAP

// Erzeugen der Parameter
Vector params = new Vector(  );
params.addElement(
    new Parameter("flightNumber", Integer.class, flightNumber, null));
params.addElement(
    new Parameter("numSeats", Integer.class, numSeats, null));
params.addElement(
    new Parameter("creditCardType", String.class, creditCardType, null));
params.addElement(
    new Parameter("creditCardNumber", Long.class, creditCardNum, null));

// Erzeugen des Aufruf-Objekts
Call call = new Call(  );
call.setTargetObjectURI("urn:xmltoday-airline-tickets");
call.setMethodName("buyTickets");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
call.setParams(params);

// Aufrufen
Response res = call.invoke(new URL("http://rpc.middleearth.com"), "");

// Arbeiten mit der Antwort

Wie Sie sehen können, ist der eigentliche Aufruf, der durch das Objekt Call repräsentiert wird, im Speicher angesiedelt. Das erlaubt es, das Ziel des Aufrufs, die aufzurufende Methode, die Codierung, die Parameter und noch mehr Dinge, die hier nicht gezeigt werden, festzulegen. Diese Methode ist flexibler als die von XML-RPC benutzte, da hier die verschiedenen Parameter explizit definiert werden, die bei der Benutzung von XML-RPC lediglich implizit bestimmt werden. Sie werden im Rest dieses Kapitels noch mehr über diesen Aufrufprozeß erfahren. Dazu kommen noch Informationen darüber, wie SOAP auf Fehler reagiert, über die Fehler-Hierarchie und natürlich über die zurückgelieferten Resultate des Aufrufs.

Mit dieser kurzen Einleitung wissen Sie vermutlich genug, um endlich zum spaßigen Teil überzugehen. Lassen Sie mich Ihnen nun die SOAP-Implementierung vorstellen, für die ich mich entschieden habe, Ihnen erklären, warum ich mich dafür entschieden habe, und schließlich einigen Code präsentieren.

Vorbereitungen

Nachdem wir nun auf einige grundlegende Konzepte eingegangen sind, ist es an der Zeit, zum spaßigen Teil überzugehen: zum Programmieren. Sie brauchen dafür ein Produkt oder Projekt, und das ist leichter zu finden, als man denken könnte. Wenn Sie ein Java-basiertes Produkt mit SOAP-Fähigkeiten haben möchten, müssen Sie nicht lange suchen. Es gibt hier zwei Arten von Produkten: kommerzielle und freie. Wie im größten Teil dieses Buches halte ich mich auch hier von kommerziellen Produkten fern. Ich tue dies nicht, weil sie schlecht sind (im Gegenteil, einige sind wundervoll); ich tue es, weil ich möchte, daß jeder Leser dieses Buches alle Beispiele ausprobieren kann. Daher muß ein jeder auf diese Produkte zugreifen können, eine Eigenschaft, die kommerzielle Produkte nicht bieten; man muß für die Benutzung oder den Download bezahlen, und irgendwann ist der Testzeitraum zu Ende.

Das führt uns zu Open Source-Projekten. Auf diesem Gebiet sehe ich nur ein verfügbares: Apache SOAP. Online verfügbar unter http://xml.apache.org/soap, hat dieses Projekt das Ziel, ein SOAP-Toolkit unter Java zur Verfügung zu stellen. Die momentan aktuelle Version 2.2 können Sie von der Apache-Website herunterladen. Das ist die Version und das Projekt, das ich durchgehend für die Beispiele in diesem Kapitel verwenden werde.

Andere Möglichkeiten

Vor der Installation und Konfiguration von Apache SOAP werde ich an dieser Stelle einige Fragen beantworten, die Ihnen womöglich im Kopf herumspuken. Es ist wahrscheinlich klar, warum ich keine kommerziellen Produkte benutze. Sie werden jedoch eventuell über den Einsatz einiger anderer Open Source-Projekte nachdenken und sich wundern, warum ich nicht auf diese eingehe.

Wie sieht es mit IBM SOAP4J aus?

Ganz oben auf der Liste steht die SOAP-Implementierung IBM SOAP4J von IBM. Die Arbeit von IBM bildet die Grundlage für das Apache SOAP-Projekt, genau wie IBM XML4J zu dem geführt hat, was heute das Apache Xerces-XML-Parser-Projekt darstellt. Es ist zu erwarten, daß die IBM-Implementierung als Verpackung des Apache SOAP-Projekts wieder auftaucht. Das ähnelt dem, was IBMs XML4J passiert; im Moment stellt es nur eine IBM-Verpackung für Xerces dar. Damit werden einige zusätzliche Levels der Herstellerunterstützung für die Open Source-Version verfügbar, wenngleich die beiden (Apache und IBM) dieselbe Codebase benutzen.

Spielt Microsoft nicht auch mit?

Ja. Ohne Zweifel ist Microsoft und seine SOAP-Implementierung wie auch die gesamte .NET-Initiative (auf die im nächsten Kapitel noch genauer eingegangen wird) sehr wichtig. Tatsächlich wollte ich eigentlich die SOAP-Implementierung von Microsoft detailliert vorstellen, aber sie unterstützt lediglich COM-Objekte und ähnliches ohne Java-Unterstützung. Aus diesem Grunde gehört eine Besprechung dieses Themas nicht in ein Buch zum Thema Java und XML. Immerhin leistet Microsoft (entgegen den Bedenken, die man als Entwickler diesem Unternehmen gegenüber haben mag) wichtige Arbeit auf dem Gebiet der Web Services, und Sie würden einen großen Fehler begehen, diesen Hersteller abzuschreiben – zumindest in dieser Hinsicht.

Wenn Sie mit COM- oder Visual-Basic-Komponenten kommunizieren müssen, empfehle ich Ihnen dringend, sich das Microsoft SOAP-Toolkit zusammen mit einer Menge anderer SOAP-Ressourcen unter http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000523 anzuschauen.

Was ist Axis?

Diejenigen von Ihnen, die die Entwicklung des Apache-Projekts verfolgen, werden eventuell von Apache Axis gehört haben. Axis ist ein SOAP-Toolkit der nächsten Generation, das ebenfalls im Umfeld von Apache entsteht. Da SOAP (die Spezifikation, nicht die Implementierung) sich zur Zeit ziemlich schnell und radikal ändert, ist es schwer, diesbezüglich immer auf dem neuesten Stand zu bleiben. Das Erstellen einer Version von SOAP, die zugleich die momentanen Bedürfnisse erfüllt und immer mit den neuesten Entwicklungen Schritt hält, ist unglaublich schwierig. Daraus resultiert, daß das momentane Apache SOAP-Angebot ein wenig eingeschränkt ist.

Anstatt ein bestehendes Toolkit umzubauen, haben die Leute von Apache mit einer neuen Codebase und einem neuen Projekt angefangen. So entstand Axis. Außerdem war damit zu rechnen, daß sich die Benennung von SOAP ändern würde: von SOAP zu XP und endlich zu XMLP. Als Resultat daraus sollte der Name des neuen Projekts vom Namen der Spezifikation getrennt werden; daher nun »AXIS«. Natürlich ist jetzt, wo das W3C vielleicht doch die Spezifikation wieder SOAP (Version 1.2 oder Version 2.0) nennen wird, alles noch verwirrender!

Sie sollten IBM SOAP4J als Architektur 1 eines SOAP-Toolkits ansehen. Darauf folgend kommt Apache SOAP (um das es in diesem Kapitel geht) als Architektur 2. Schließlich bietet AXIS eine Architektur der nächsten Generation oder Architektur 3. Diese Architektur wird durch SAX angetrieben, während Apache SOAP auf DOM aufsetzt. Zusätzlich bietet AXIS eine benutzerfreundlichere Art der Header-Interaktion. Das ist etwas, das man in Apache-SOAP nicht findet.

Bei all diesen Verbesserungen wundern Sie sich vielleicht, warum ich Axis nicht vorstelle. Es ist einfach zu früh. Momentan versucht Axis, das 0.51-Release fertigzustellen. Es ist keine Beta, es ist nicht einmal eine Alpha; es ist wirklich ein sehr frühes Stadium. Ich würde liebend gern all die neuen Features von Axis vorstellen, es gibt jedoch keine Möglichkeit, Ihren Boss davon zu überzeugen, eine Pre-Alpha einer Open Source Software in unternehmenskritischen Bereichen einzusetzen, oder? Daher habe ich mich dazu entschlossen, etwas zu besprechen, das Sie heute einsetzen können: Apache SOAP. Ich bin sicher, daß ich dieses Kapitel in einer Neuauflage dieses Buches aktualisieren werde, wenn Axis fertiggestellt ist. Bis dahin werden wir uns auf eine benutzbare Lösung konzentrieren.

Die Installation

Es existieren, was SOAP betrifft, zwei Arten der Installation. Die erste besteht darin, einen SOAP-Client laufen zu lassen, der die SOAP-API benutzt, um mit einem Server zu kommunizieren, der SOAP-Botschaften versteht. Die zweite ist, einen SOAP-Server laufenzulassen, der Botschaften von SOAP-Clients empfangen kann. Beide Arten werden in diesem Abschnitt vorgestellt.

Der Client

Um SOAP auf einem Client zu benutzen, müssen Sie zunächst Apache SOAP unter http://www.apache.org/dyn/closer.cgi/ws/soap/ herunterladen. Ich habe mir Version 2.2 als Binary (im Unterverzeichnis version-2.2) geholt. Anschließend entpacken Sie die Inhalte des Archivs in ein Verzeichnis auf Ihrem Rechner. Meine Installation befindet sich im Verzeichnis javaxml2 (c:\javaxml2 auf meinem Windows-Rechner und /javaxml2 auf meinem Rechner mit Mac OS X). Das Ergebnis ist das Verzeichnis /javaxml2/soap-2_2. Sie müssen außerdem das Package JavaMail bei SUN unter http://java.sun.com/products/javamail/ herunterladen. Es wird zur Unterstützung des Simple Mail Transfer Protocol in Apache SOAP benötigt. Außerdem müssen Sie das JavaBeans Activation Framework (JAF), das auch von SUN stammt, von http://java.sun.com/products/beans/glasgow/jaf.html herunterladen. Ich nehme an, daß Sie Xerces oder einen anderen XML-Parser noch verfügbar haben.

Vergewissern Sie sich, daß der von Ihnen verwendete XML-Parser JAXP-kompatibel ist und mit Namensräumen umgehen kann. Ihr Parser wird wahrscheinlich beide Bedingungen erfüllen, es sei denn, er ist ein absoluter Spezialfall. Haben Sie dennoch Probleme, sollten Sie wieder Xerces benutzen.

Benutzen Sie eine aktuelle Version von Xerces; Version 1.4 oder höher sollte ausreichen. Es gibt eine Menge Probleme mit SOAP und Xerces 1.3(.1), also meide ich diese Kombination wie die Pest.

Sie müssen nun die Packages JavaMail und JAF entpacken und die mitgelieferten jar-Dateien wie auch die Datei soap.jar zum Klassenpfad hinzufügen. Diese jar-Dateien befinden sich entweder im Wurzelverzeichnis oder im Verzeichnis lib/ der betreffenden Distribution. Danach sollte der Klassenpfad ähnlich wie hier gezeigt aussehen:

$ echo $CLASSPATH
/javaxml2/soap-2_2/lib/soap.jar:/javaxml2/lib/xerces.jar:
/javaxml2/javamail-1.2/mail.jar:/javaxml2/jaf-1.0.1/activation.jar

Unter Windows sollte er ungefähr so aussehen:

c:\>echo %CLASSPATH%
c:\javaxml2\soap-2_2\lib\soap.jar;c:\javaxml2\lib\xerces.jar;
c:\javaxml2\javamail-1.2\mail.jar;c:\javaxml2\jaf-1.0.1\activation.jar

Schließlich müssen Sie noch das Verzeichnis javaxml2/soap-2_2/ zum Klassenpfad hinzufügen, wenn Sie die SOAP-Beispiele laufen lassen möchten. Ich gehe auf das Setup verschiedener Beispiele in diesem Kapitel ein, wenn ich die Beispiele selbst behandle.

Der Server

Um einen Satz SOAP-fähiger Server-Komponenten zu schaffen, benötigen Sie zunächst einmal eine Servlet-Engine. Wie in den vorangegangenen Kapiteln benutze ich Apache Tomcat (verfügbar unter http://jakarta.apache.org) in diesem Kapitel für die Beispiele. Sie müssen alles zum Klassenpfad hinzufügen, was bereits für den Client gebraucht wurde. Der einfachste Weg ist, die Dateien soap.jar, activation.jar und mail.jar wie auch Ihren Parser in das Bibliotheksverzeichnis Ihrer Servlet-Engine zu kopieren. Für Tomcat ist das einfach das Verzeichnis lib/, in dem sich die Bibliotheken befinden, die automatisch geladen werden sollen. Wenn Sie das Scripting unterstützen wollen (was nicht hier abgedeckt wird, aber Teil der Apache SOAP-Beispiele ist), müssen Sie bsf.jar (unter http://oss.software.ibm.com/developerworks/projects/bsf) und js.jar (unter http://www.mozilla.org/rhino/) ebenfalls in dieses Verzeichnis kopieren.

Wenn Sie Xerces zusammen mit Tomcat benutzen, müssen Sie denselben Umbenennungstrick anwenden, den ich schon im Kapitel Web Publishing Frameworks vorgestellt habe. Die Datei parser.jar wird in z_parser.jarund jaxp.jar wird in z_jaxp.jar umbenannt, um sicherzustellen, daß xerces.jar und die beigefügte Version von JAXP vor anderen Parser- oder JAXP-Implementierungen geladen werden.

Nach einem Neustart der Servlet-Engine können Sie nun damit beginnen, SOAP-Server-Komponenten zu entwickeln.

Das Router-Servlet und der Admin-Client

Zusätzlich zur grundlegenden Funktionalität liefert Apache SOAP ein Router-Servlet und einen Admin-Client mit. Auch wenn Sie diese nicht benutzen möchten, empfehle ich, sie zu installieren, damit Sie Ihre SOAP-Implementierung testen können. Dieser Prozeß ist für die verwendete Servlet-Engine spezifisch, also decke ich hier nur die Tomcat-Installation ab. Anweisungen zur Installation für einige andere Servlet-Engines sind unter http://xml.apache.org/soap/docs/index.html verfügbar.

Die Installation unter Tomcat ist simpel; dazu müssen Sie lediglich die Datei soap.war aus dem Verzeichnis soap-2_2/webapps in das Verzeichnis $TOMCAT_HOME/webapps kopieren. Das ist alles! Um die Installation zu testen, surfen Sie mit Ihrem Browser zu der Adresse http://localhost:8080/soap/servlet/rpcrouter. Die Antwort sollte aussehen wie in Abbildung 12-2.

Abbildung 12-2: Das RPC-Router-Servlet
Das RPC-Router-Servlet

Auch wenn das wie eine Fehlermeldung aussieht, sagt es doch aus, daß alles korrekt arbeitet. Sie sollten dieselbe Antwort beim Zugriff auf den Admin-Client unter der Adresse http://localhost:8080/soap/servlet/messagerouter sehen.

Für einen abschließenden Test von Server und Client stellen Sie sicher, daß Sie den Setup-Instruktionen bis hierher gefolgt sind. Nun führen Sie die folgende Java-Klasse wie gezeigt aus, wobei Sie die URL des RPC-Router-Servlets angeben:

C:\>java org.apache.soap.server.ServiceManagerClient 
         http://localhost:8080/soap/servlet/rpcrouter list
Deployed Services:

Sie sollten, wie hier gezeigt, eine leere Liste von Services sehen. Falls Sie irgendeine andere Meldung sehen, schauen Sie bitte in die Liste möglicher Fehler unter http://xml.apache.org/soap/docs/trouble/index.html. Dort existiert eine ziemlich vollständige Liste von möglichen Problemen. Wenn Sie eine leere Liste von Services sehen, sind Sie bereit, mit den Beispielen im Rest dieses Kapitels fortzufahren.

Machen wir uns die Hände schmutzig!

Drei grundlegende Schritte gehören zur Entwicklung eines SOAP-basierten Systems, und ich werde nacheinander einen Blick auf jeden davon werfen:

  • Entscheidung zwischen SOAP-RPC und SOAP-Messaging treffen
  • Einen SOAP-Service selber schreiben oder sich Zugriff auf einen verschaffen
  • Einen SOAP-Client selber schreiben oder sich Zugriff auf einen verschaffen

Zunächst müssen Sie entscheiden, ob Sie SOAP für Aufrufe im Stil von RPC nutzen wollen, wobei Prozeduren auf einem Server abgearbeitet werden, oder für das sogenannte Messaging, wobei ein Client einfach Informationen zum Server schickt. Auf diese Prozesse wird im nächsten Abschnitt detailliert eingegangen. Haben Sie diese Design-Entscheidung getroffen, brauchen Sie entweder Zugriff auf einen Service, oder Sie müssen einen solchen Service programmieren. Da wir alle Java-Profis sind, beschreibt dieses Kapitel, wie man selbst einen Service schreibt. Zu guter Letzt muß dann noch ein Client geschrieben werden, danach brauchen Sie nur noch zuzusehen, wie es losgeht.

RPC oder Messaging?

Die erste Aufgabe betrifft nicht die Programmierung, sondern das Design. Sie müssen entscheiden, ob ein RPC-Service oder ein Messaging-Service verwendet werden soll. Die erste Alternative ist RPC. Das ist etwas, womit Sie nach dem letzten Kapitel eigentlich recht vertraut sein sollten. Ein Client ruft eine Prozedur auf irgendeinem entfernten Server auf und erhält daraufhin irgendeine Antwort. In diesem Szenario arbeitet SOAP einfach als ein erweiterbareres XML-RPC-System, das eine bessere Fehlerbehandlung und das Verschicken komplexer Datentypen über das Netz erlaubt. Dieses Konzept sollten Sie nunmehr verstanden haben, und da es so aussieht, als ob RPC-Systeme mit SOAP einfach zu programmieren sind, werde ich damit beginnen. Dieses Kapitel beschreibt, wie Sie einen RPC-Service und danach einen RPC-Client schreiben und wie das ganze System schließlich die Arbeit aufnimmt.

Die zweite Art der SOAP-Verarbeitung erfolgt auf der Grundlage von Botschaften (engl.: messages). Anstatt Prozeduren aufzurufen, ergibt sich hier die Möglichkeit, Informationen zu übertragen. Wie Sie sich vorstellen können, ist diese Variante ziemlich leistungsfähig: Der Client muß nicht über eine bestimmte Methode auf dem Server Bescheid wissen. Es modelliert außerdem verteilte Systeme genauer durch die Möglichkeit, Datenpakete (Paket hier im übertragenen Sinne, nicht als Netzwerkpaket) herumzuschicken, was verschiedenen Systemen die Möglichkeit gibt zu beobachten, was andere tun. Dieses Verfahren ist auch komplizierter als die simple RPC-Programmierung, daher werde ich es im nächsten Kapitel zusammen mit anderen Business-to-Business-Details behandeln, nachdem Sie sich in der SOAP-RPC-Programmierung richtig gut auskennen.

Wie die meisten Design-Fragen bleibt Ihnen auch hier der Prozeß der Entscheidungsfindung selbst überlassen. Schauen Sie sich Ihre Anwendung genau an, und ermitteln Sie, was genau SOAP für Sie tun soll. Wenn Sie einen Server haben und eine Menge von Clients, die nur irgendwelche Aufgaben auslagern sollen, wird RPC wahrscheinlich Ihren Ansprüchen genügen. In größeren Systemen jedoch, wo es mehr darum geht, Daten auszutauschen, als spezifische Geschäftsfunktionen auf Anfrage auszuführen, sind die Messaging-Fähigkeiten von SOAP die bessere Wahl.

Ein RPC-Service

Nun, da die Formalitäten geklärt sind, ist es an der Zeit, schnell und entschlossen loszulegen. Wie Sie sicher noch vom letzten Kapitel wissen, benötigt man bei RPC eine Klasse, deren Methoden von Clients aus aufgerufen werden können.

Code-Artefakte

Ich werde damit beginnen, daß ich Ihnen einige Code-Artefakte präsentiere, die auf dem Server verfügbar sein sollen. Diese Artefakte stellen Klassen dar, die Methoden für RPC-Clients zur Verfügung stellen.2 Anstatt die eher einfache Klasse aus dem letzten Kapitel zu benutzen, präsentiere ich hier ein etwas komplexeres Beispiel, um zu zeigen, was SOAP kann. In diesem Sinne ist Beispiel 12-4 eine Klasse, die einen CD-Lagerbestand speichert, wie sie eine Anwendung für einen Online-Musik-Shop gebrauchen könnte. Ich zeige hier eine Basisversion, die ich später in diesem Kapitel noch erweitern werde.

Beispiel 12-4: Die Klasse CDCatalog

package javaxml2;

import java.util.Hashtable;

public class CDCatalog {

    /** Die CDs mit dem Titel als Schlüssel */
    private Hashtable catalog;

    public CDCatalog(  ) {
        catalog = new Hashtable(  );

        // Katalog bestücken
        catalog.put("Nickel Creek", "Nickel Creek");
        catalog.put("Let it Fall", "Sean Watkins");
        catalog.put("Aerial Boundaries", "Michael Hedges");
        catalog.put("Taproot", "Michael Hedges");
    }

    public void addCD(String title, String artist) {
        if ((title == null) || (artist == null)) {
         throw new IllegalArgumentException("Titel/Künstler dürfen nicht null sein.");
        }
        catalog.put(title, artist);        
    }

    public String getArtist(String title) {
        if (title == null) {
            throw new IllegalArgumentException("Titel darf nicht null sein.");
        }

        // Rückgabe der angeforderten CD
        return (String)catalog.get(title);
    }

    public Hashtable list(  ) {
        return catalog;
    }
}

Dies erlaubt das Hinzufügen einer neuen CD, das Suchen von Künstlern über einen CD-Titel und eine Auflistung aller CDs. Beachten Sie bitte, daß die Methode list( ) eine Hashtable liefert und daß Sie dazu nichts besonderes tun müssen – Apache SOAP stellt ein automatisches Mapping für den Java-Typ Hashtable zur Verfügung, wie es auch bei XML-RPC der Fall war.

Kompilieren Sie die Klasse, und stellen Sie sicher, daß Sie alles korrekt eingegeben (oder heruntergeladen, wenn Sie dieses Vorgehen bevorzugen) haben. Beachten Sie, daß die Klasse CDCatalog keine Ahnung von SOAP hat. Das bedeutet, Sie können Ihre bereits vorhandenen Java-Klassen nehmen und sie mittels SOAP-RPC zur Verfügung stellen, was den Arbeitsaufwand auf Ihrer Seite bei der Umstellung auf eine SOAP-basierte Architektur extrem verringert.

Deployment-Deskriptoren

Ist die Java-Programmierung abgeschlossen, müssen Sie einen Deployment-Deskriptor definieren. Dieser spezifiziert einige Schlüsselattribute für den SOAP-Server:

  • die URN des SOAP-Services, auf den Clients zugreifen sollen
  • die Methode(n), auf die die Clients zugreifen dürfen
  • die Handler für Serialisierung und Deserialisierung für benutzerdefinierte Klassen

Das erste ähnelt einer URL und wird unbedingt für einen Client benötigt, der Verbindung mit einem SOAP-Server aufnehmen soll. Das zweite ist exakt das, was Sie erwarten: eine Liste von Methoden, die alle erlaubten Artefakte für einen SOAP-Client beschreibt. Es erklärt dem SOAP-Server, den ich gleich erläutere, außerdem, welche Requests er annehmen soll. Das dritte ist ein Mittel, dem SOAP-Server mitzuteilen, wie er mit benutzerdefinierten Parametern umgehen soll; ich komme darauf im nächsten Abschnitt zurück, wenn ich einige komplexere Verhaltensweisen zum Katalog hinzufüge.

Ich zeige Ihnen nun den Deployment-Deskriptor und erläutere jedes Element darin. Beispiel 12-5 ist der Deployment-Deskriptor für den von uns erzeugten CDCatalog-Service.

Beispiel 12-5: Der Deployment-Deskriptor für den CDCatalog

<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
             id="urn:cd-catalog"
>
  <isd:provider type="java"
                scope="Application"
                methods="addCD getArtist list"
  >
    <isd:java class="javaxml2.CDCatalog" static="false" />
  </isd:provider>

  <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service>

Zuerst habe ich den Apache SOAP-Deployment-Namensraum referenziert und anschließend mit dem Attribut id eine URN für meinen Service angegeben. Ich habe ungefähr genausoviel Originalität an den Tag gelegt wie Dave Matthews bei der Benennung seiner Band, aber schließlich wird der Anforderung damit Genüge getan. Anschließend spezifiziere ich mittels des Elements java die Klasse, die zur Verfügung gestellt werden soll, einschließlich des Namens ihres Packages (durch das Attribut class) und gebe an, daß es sich dabei nicht um static-Methoden handelt (durch das Attribut static).

Danach gebe ich an, welche Fault-Listener-Implementierung benutzt werden soll. Die SOAP-Implementierung von Apache stellt zwei zur Auswahl. Ich benutze den DOMFaultListener. Dieser Listener liefert jede Exception und Fehlerinformation über ein zusätzliches DOM-Element in der Antwort zum Client zurück. Ich komme darauf zurück, wenn es um das Schreiben von Clients geht. Sie müssen sich also jetzt nicht darum kümmern. Die andere Fault-Listener-Implementierung ist org.apache.soap.server.ExceptionFaultListener. Dieser Listener stellt alle Fehler mittels eines zusätzlichen an den Client zurückgegebenen Parameters dar. Da schon ziemlich viele SOAP-basierte Anwendungen in Java und XML-APIs wie DOM arbeiten, wird gemeinhin der DOMFaultListener benutzt.

Das Installieren des Service

An diesem Punkt besitzen Sie einen funktionsfähigen Deployment-Deskriptor und eine Menge an Code-Artefakten, die Sie nach außen verfügbar machen können. Damit können Sie die Installation Ihres Service ins Auge fassen. Apache SOAP bringt dafür ein Tool mit, wenn Sie das Setup korrekt durchgeführt haben. Zunächst brauchen Sie einen Deployment-Deskriptor für den Service. Ein solcher wurde gerade gesprochen. Außerdem müssen dem SOAP-Server die entsprechenden Klassen zugänglich gemacht werden. Der beste Weg ist, die Service-Klasse aus dem letzten Abschnitt in eine jar-Datei zu packen:

jar cvf javaxml2.jar javaxml2/CDCatalog.class

Kopieren Sie diese jar-Datei in das Verzeichnis lib/ (beziehungsweise in das Verzeichnis, aus dem Bibliotheken von Ihrer Servlet-Engine automatisch geladen werden), und starten Sie die Servlet-Engine neu.

Wenn Sie das getan haben, haben Sie einen Snapshot Ihrer Klasse angelegt. Das Ändern und Neukompilieren der Datei CDCatalog.java reicht nicht, damit die Servlet-Engine diese Änderungen verwendet. Dazu müssen Sie die entstandene class-Datei wiederum in die jar-Datei packen und diese in das entsprechende Verzeichnis kopieren. Um sicherzustellen, daß die Servlet-Engine den aktuellen Code verwendet, müssen Sie diese anschließend neu starten.

Wenn der SOAP-Server in der Lage ist, auf Ihre Service-Klasse(n) zuzugreifen, kann das Installieren des Services unter Verwendung der Tool-Klasse org.apache.soap.server. ServiceManager von Apache SOAP durchgeführt werden:

C:\javaxml2\Ch12>java org.apache.soap.server.ServiceManagerClient 
    http://localhost:8080/soap/servlet/rpcrouter deploy xml\CDCatalogDD.xml

Das erste Argument ist der SOAP-Server und das RPC-Router-Servlet, das zweite die auszuführende Aktion und das dritte der betreffende Deployment-Deskriptor. Wenn das ausgeführt wurde, können Sie folgendermaßen überprüfen, daß der Service hinzugefügt wurde:

(gandalf)/javaxml2/Ch12$ java org.apache.soap.server.ServiceManagerClient 
    http://localhost:8080/soap/servlet/rpcrouter list
Deployed Services:
        urn:cd-catalog
        urn:AddressFetcher
        urn:xml-soap-demo-calculator

Auf eine ziemlich puristische Art und Weise sollte das alle auf diesem Server verfügbaren Services anzeigen. Schließlich können Sie den Service auch wieder leicht entfernen, wenn Sie seinen Namen kennen:

C:\javaxml2\Ch12>java org.apache.soap.server.ServiceManagerClient 
    http://localhost:8080/soap/servlet/rpcrouter undeploy urn:cd-catalog

Jedesmal, wenn der Code des Service modifiziert wird, sollten Sie den Service entfernen und das Installieren wiederholen, um sicherzustellen, daß der SOAP-Server den aktuellsten Code benutzt.

Ein RPC-Client

Als nächstes ist der Client an der Reihe. Ich werde alles sehr einfach halten und schnell ein paar Kommandozeilen-Programme schreiben, die SOAP-RPC verwenden. Es ist unmöglich, Ihr spezifisches Szenario zu erraten, also konzentriere ich mich auf die SOAP-Details und überlasse Ihnen die Integration in Ihre existierende Software. Wenn Sie den szenariospezifischen Teil Ihrer Anwendung fertighaben, sind einige grundlegende Schritte für jeden SOAP-RPC-Aufruf auszuführen:

  • Erzeugen des SOAP-RPC-Aufrufs
  • Einstellen der Typ-Mappings für alle benutzerdefinierten Parameter
  • Setzen der URI des zu benutzenden SOAP-Servers
  • Angeben der aufzurufenden Methode
  • Angeben der zu benutzenden Codierung
  • Hinzufügen aller Parameter zu dem Aufruf
  • Verbinden mit dem SOAP-Server
  • Empfangen und Interpretieren der Antwort

Das sieht nach einer Menge Arbeit aus. Die meisten dieser Operationen sind jedoch mit einer oder zwei Zeilen an Methodenaufrufen erledigt. Mit anderen Worten: Die Kommunikation mit einem SOAP-Service ist ein Kinderspiel. Beispiel 12-6 zeigt den Code für die Klasse CDAdder, die benutzt werden kann, um neue CDs zum Katalog hinzuzufügen. Schauen Sie sich zunächst den Code an, anschließend werden wir die interessanten Teile näher betrachten.

Beispiel 12-6: Die Klasse CDAdder

package javaxml2;

import java.net.URL;
import java.util.Vector;
import org.apache.soap.Constants;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;

public class CDAdder {

    public void add(URL url, String title, String artist) 
        throws SOAPException {

        System.out.println("Füge CD mit dem Titel '" + title + "' von '" +
            artist + "' hinzu");

        // Erzeugen des Aufruf-Objekts
        Call call = new Call(  );
        call.setTargetObjectURI("urn:cd-catalog");
        call.setMethodName("addCD");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

        // Setzen der Parameter
        Vector params = new Vector(  );
        params.addElement(new Parameter("title", String.class, title, null));
        params.addElement(new Parameter("artist", String.class, artist, null));
        call.setParams(params);

        // Aufrufen
        Response response;
        response = call.invoke(url, "");

        if (!response.generatedFault(  )) {
            System.out.println("CD erfolgreich hinzugefügt.");
        } else {
            Fault fault = response.getFault(  );
            System.out.println("Fehler: " + fault.getFaultString(  ));
        }
    }

    public static void main(String[] args) {
        if (args.length != 3) {
            System.out.println("Benutzung: java javaxml2.CDAdder [SOAP-Server-URL] " +
                "\"[CD-Titel]\" \"[Künstlername]\"");
            return;
        }

        try {
            // URL des SOAP-Servers
            URL url = new URL(args[0]);

            // Die Beschreibung einer neuen CD holen
            String title = args[1];
            String artist = args[2];

            // Hinzufügen der CD
            CDAdder adder = new CDAdder(  );
            adder.add(url, title, artist);
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Dieses Programm holt sich die URL des SOAP-Servers, zu dem eine Verbindung hergestellt werden soll, und die Informationen, die benötigt werden, um eine neue CD zum Katalog hinzuzufügen. Anschließend wird in der Methode add( ) das SOAP-Call-Objekt erzeugt, über das die gesamte interessante Kommunikation abläuft. Die Ziel-URI des SOAP-Service und die aufzurufende Methode werden für dieses Objekt angegeben. Beide stimmen mit Einträgen aus dem Deployment-Deskriptor in Beispiel 12-5 überein. Als nächstes wird die Codierung festgelegt. Das sollte immer die Konstante Constants.NS_URI_SOAP_ENC sein, wenn Sie nicht völlig exotische Bedürfnisse befriedigen müssen.

Das Programm erzeugt zunächst eine Instanz der Klasse Vector und füllt sie mit SOAP-Parameter-Objekten. Jedes repräsentiert einen Parameter für die spezifizierte Methode, und da die Methode addCD( ) zwei Strings verlangt, ist das ziemlich einfach. Dazu geben Sie den Namen (für die Benutzung in XML und zum Debuggen), die Klasse des Parameters und den eigentlichen Wert an. Das vierte Element ist optional und stellt die Codierung dar, wenn ein Parameter eine spezielle benötigt. Wenn das nicht der Fall ist, reicht null. Der resultierende Vector wird anschließend zum Call-Objekt hinzugefügt.

Wenn der Aufruf fertig vorbereitet ist, wird die Methode invoke( ) dieses Objektes aufgerufen. Der Rückgabewert dieses Aufrufs ist eine Instanz der Klasse org.apache. soap.Response, von der abgefragt wird, ob irgendwelche Probleme auftraten. Das ist ziemlich selbsterklärend, also überlasse ich es Ihnen, den Code zu analysieren. Haben Sie den Client kompiliert und sind Sie den Anweisungen in diesem Kapitel bezüglich des Klassenpfades bis hierher gefolgt, können Sie das Beispiel wie folgt starten:

C:\javaxml2\build>java javaxml2.CDAdder 
   http://localhost:8080/soap/servlet/rpcrouter 
   "Riding the Midnight Train" "Doc Watson"

Füge CD mit dem Titel 'Riding the Midnight Train' von 'Doc Watson' hinzu
CD erfolgreich hinzugefügt.

Beispiel 12-7 stellt eine weitere einfache Klasse CDLister dar, die alle momentan im Katalog enthaltenen CDs auflistet. Ich werde hier nicht zu sehr auf Details eingehen, da sie Beispiel 12-6 sehr ähnelt und eigentlich nur eine Vertiefung dessen darstellt, worüber ich bereits gesprochen habe.

Beispiel 12-7: Die Klasse CDLister

package javaxml2;

import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.apache.soap.Constants;
import org.apache.soap.Fault;
import org.apache.soap.SOAPException;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;

public class CDLister {

    public void list(URL url) throws SOAPException {
        System.out.println("Auflistung des Inhalts des CD-Katalogs.");

        // Erzeugen des Aufruf-Objekts
        Call call = new Call(  );
        call.setTargetObjectURI("urn:cd-catalog");
        call.setMethodName("list");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

        // Keine Parameter nötig

        // Aufrufen
        Response response;
        response = call.invoke(url, "");

        if (!response.generatedFault(  )) {
            Parameter returnValue = response.getReturnValue(  );
            Hashtable catalog = (Hashtable)returnValue.getValue(  );
            Enumeration e = catalog.keys(  );
            while (e.hasMoreElements(  )) {
                String title = (String)e.nextElement(  );
                String artist = (String)catalog.get(title);
                System.out.println("  '" + title + "' von " + artist);
            }
        } else {
            Fault fault = response.getFault(  );
            System.out.println("Fehler: " + fault.getFaultString(  ));
        }
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Benutzung: java javaxml2.CDAdder [SOAP-Server-URL]");
            return;
        }

        try {
            // URL des SOAP-Servers
            URL url = new URL(args[0]);

            // Auflistung aller enthaltenen CDs
            CDLister lister = new CDLister(  );
            lister.list(url);
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Der einzige Unterschied zur Klasse CDAdder besteht in dieser Methode darin, daß das Response-Objekt einen Rückgabewert hat (die Hashtable der Methode list( )). Sie wird als Parameter-Objekt zurückgegeben, was es dem Client erlaubt, die Codierung zu prüfen und dann den eigentlichen Rückgabewert zu extrahieren. Wenn das getan ist, kann der Client den zurückgelieferten Wert wie jedes andere Java-Objekt benutzen. Im Beispiel wird einfach der gesamte Katalog durchgegangen und jede CD auf dem Bildschirm ausgegeben. Sie können nun diesen Client ebenfalls starten, um die Ergebnisse anzuschauen:

C:\javaxml2\build>java javaxml2.CDLister 
    http://localhost:8080/soap/servlet/rpcrouter
Auflistung des Inhalts des CD-Katalogs.
  'Riding the Midnight Train' von Doc Watson
  'Taproot' von Michael Hedges
  'Nickel Creek' von Nickel Creek
  'Let it Fall' von Sean Watkins
  'Aerial Boundaries' von Michael Hedges

Das ist wirklich alles, was es zur RPC-Grundfunktionalität in SOAP zu sagen gibt. Ich möchte es jedoch ein wenig vorantreiben und zu einigen komplexeren Themen kommen.

Erforschen weiterer Gebiete

Sie wissen jetzt, wie man all das, was Sie bereits mit XML-RPC machen können, auch mit SOAP erreichen. SOAP bietet jedoch noch einiges mehr. Wie ich schon zu Beginn dieses Kapitels sagte, sind zwei wichtige Dinge, die SOAP mit sich bringt, die Möglichkeit, benutzerdefinierte Typen mit minimalem Aufwand einzusetzen, und die verbesserte Fehlerbehandlung. In diesem Abschnitt werde ich auf beide Themen eingehen.

Benutzerdefinierte Parametertypen

Die größte Beschränkung des CD-Katalogs ist, zumindest zu diesem Zeitpunkt, daß er nur Titel und Künstler der CD speichern kann. Es ist realistischer, daß ein Objekt (oder eine Menge von Objekten) eine CD mit ihrem Titel, dem Namen des Künstlers, der Plattenfirma, den Titeln, möglicherweise dem Genre und allen möglichen anderen Informationen beschreibt. Ich werde nicht diese gesamte Architektur benutzen, sondern von der Repräsentation mit Titel und Künstlername zur Repräsentation durch ein Objekt mit Titel, Künstlername und Plattenfirma übergehen. Dieses Objekt muß vom Server zum Client und wieder zurück übergeben werden, was demonstriert, wie SOAP mit komplexen Datentypen umgeht. Beispiel 12-8 zeigt diese neue Klasse.

Beispiel 12-8: Die Klasse CD

package javaxml2;

public class CD {

    /** Der Titel der CD */
    private String title;

    /** Der Künstler, der auf der CD zu hören ist*/
    private String artist;

    /** Die Plattenfirma der CD */
    private String label;

    public CD(  ) {
        // Default-Konstruktor
    }

    public CD(String title, String artist, String label) {
        this.title = title;
        this.artist = artist;
        this.label = label;
    }

    public String getTitle(  ) { 
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getArtist(  ) {
        return artist;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public String getLabel(  ) {
        return label;
    }
 
    public void setLabel(String label) {
        this.label = label;
    }

    public String toString(  ) {
        return "'" + title + "' von " + artist + ", bei " +
            label;
    }
}

Das erfordert ebenfalls massive Änderungen an der Klasse CDCatalog. Beispiel 12-9 zeigt eine modifizierte Version dieser Klasse, in der alle zur Benutzung des neuen CD-Objekts nötigen Änderungen hervorgehoben sind.

Beispiel 12-9: Die aktualisierte Klasse CDCatalog

package javaxml2;

import java.util.Hashtable;

public class CDCatalog {

    /** Die CDs; wieder wirkt der Titel als Schlüssel */
    private Hashtable catalog;

    public CDCatalog(  ) {
        catalog = new Hashtable(  );

        // Bestücken des Kataloges
        addCD(new CD("Nickel Creek", "Nickel Creek", "Sugar Hill"));
        addCD(new CD("Let it Fall", "Sean Watkins", "Sugar Hill"));
        addCD(new CD("Aerial Boundaries", "Michael Hedges", "Windham Hill"));
        addCD(new CD("Taproot", "Michael Hedges", "Windham Hill"));
    }

    public void addCD(CD cd) {
        if (cd == null) {
            throw new IllegalArgumentException("Das CD-Objekt darf nicht null sein.");
        }
        catalog.put(cd.getTitle(  ), cd);        
    }

    public CD getCD(String title) {
        if (title == null) {
            throw new IllegalArgumentException("Der Titel darf nicht null sein.");
        }

        // Rückgabe der angeforderten CD
        return (CD)catalog.get(title);
    }

    public Hashtable list(  ) {
        return catalog;
    }
}

Zusätzlich zu den offensichtlichen Änderungen habe ich ebenfalls die alte Methode getArtist(String title) durch die Methode getCD(String title) ersetzt, die als Rückgabetyp jetzt die Klasse CD hat. Das bedeutet, daß der SOAP-Server die neue Klasse nun serialisieren und deserialisieren muß und daß der Client aktualisiert wird. Zuerst werde ich einen Blick auf den aktualisierten Deployment-Deskriptor werfen, der die Einzelheiten der Serialisierung dieses benutzerdefinierten Typs beschreibt. Fügen Sie die folgenden Zeilen zu dem Deployment-Deskriptor für den CD-Katalog hinzu, und ändern Sie auch gleich den Namen der verfügbaren Methoden, um die Änderungen der Klasse CDCatalog widerzuspiegeln:

<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
             id="urn:cd-catalog"
>
  <isd:provider type="java"
                scope="Application"
                methods="addCD getCD list"
  >
    <isd:java class="javaxml2.CDCatalog" static="false" />
  </isd:provider>

  <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>

  <isd:mappings>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="urn:cd-catalog-demo" qname="x:cd"
             javaType="javaxml2.CD"
             java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
  </isd:mappings>
</isd:service>

Das neue Element mappings gibt an, wie ein SOAP-Server benutzerdefinierte Typen wie die Klasse CD behandeln soll. Zunächst wird ein Element map für jeden benutzerdefinierten Typ definiert. Als Wert für das Attribut encodingStyle sollten Sie zumindest mit Apache SOAP 2.2 immer http://schemas.xmlsoap.org/soap/encoding/ nutzen, da das zur Zeit die einzige Codierung ist, die unterstützt wird. Sie müssen einen Namensraum für den benutzerdefinierten Typ und dann den Namen der Klasse mit dem vorangestellten Namensraum-Präfix für den Typ angeben. Ich habe einen »Dummy«-Namensraum benutzt und das Präfix einfach »x« genannt. Anschließend wird mittels des Attributs javaType der eigentliche Klassenname unter Java spezifiziert: in diesem Falle javaxml2.CD. Endlich kommt dann die Magie in den Attributen java2XMLClassName und xml2JavaClass-Name zum Zuge.

Sie spezifizieren eine Klasse, die von Java zu XML und umgekehrt konvertiert. Ich benutze die unglaublich nützliche Klasse BeanSerializer, die ebenfalls in Apache SOAP enthalten ist. Wenn Ihre Klasse im Format einer JavaBean vorliegt, können Sie sich durch die Benutzung dieser Serializer/Deserializer-Klasse das Schreiben einer eigenen sparen. Dazu muß die Klasse über einen Default-Konstruktor (erinnern Sie sich: Ich habe der Klasse CD einen leeren, parameterlosen Konstruktor gegeben.) und alle Felder der Klasse über setXXX- und getXXX-Methoden verfügen. Da die Klasse CD diesen Anforderungen entspricht, erfüllt die Klasse BeanSerializer hier ihren Zweck.

Es ist kein Zufall, daß die Klasse CD den JavaBean-Konventionen folgt. Die meisten Datenklassen passen gut in dieses Format, und ich wußte, daß ich keinen eigenen Serializer/Deserializer schreiben wollte. Sie sind schwierig zu schreiben (zwar ist das Schreiben nicht wirklich schwer, aber es ist sehr leicht, Fehler zu machen), und ich lege Ihnen sehr ans Herz zu versuchen, eigene Parametertypen nach den Bean-Konventionen zu schreiben. In vielen Fällen brauchen Sie dazu nur einen Default-Konstruktor (parameterlos) in die Klasse einzufügen.

Nun müssen Sie die jar-Datei für den Service neu erstellen und das Installieren wiederholen:

(gandalf)/javaxml2/Ch12$ java org.apache.soap.server.ServiceManagerClient 
    http://localhost:8080/soap/servlet/rpcrouter xml/CDCatalogDD.xml

Wenn Ihre Servlet-Engine während der Installation noch lief, müssen Sie sie von neuem starten, um die neuen Klassen zu nutzen.

An diesem Punkt angekommen, müssen nur noch die Clients modifiziert werden, um mit dem neuen Typ und den neuen Methoden umgehen zu können. Beispiel 12-10 stellt eine aktualisierte Version des Clients CDAdder dar. Die Änderungen gegenüber der vorhergehenden Version sind hervorgehoben.

Beispiel 12-10: Die aktualisierte Klasse CDAdder

package javaxml2;

import java.net.URL;
import java.util.Vector;
import org.apache.soap.Constants;
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.util.xml.QName;

public class CDAdder {

    public void add(URL url, String title, String artist, String label) 
        throws SOAPException {

        System.out.println("CD hinzufügen '" + Titel + "' von '" +
            Künstlername + "', on the label " + label);

        CD cd = new CD(title, artist, label);

        // Mapping des Typs, so daß SOAP ihn benutzen kann
        SOAPMappingRegistry registry = new SOAPMappingRegistry(  );
        BeanSerializer serializer = new BeanSerializer(  );
        registry.mapTypes(Constants.NS_URI_SOAP_ENC,
            new QName("urn:cd-catalog-demo", "cd"),
            CD.class, serializer, serializer);   

        // Erzeugen des Aufruf-Objekts
        Call call = new Call(  );
        call.setSOAPMappingRegistry(registry);
        call.setTargetObjectURI("urn:cd-catalog");
        call.setMethodName("addCD");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

        // Setzen der Parameter
        Vector params = new Vector(  );
        params.addElement(new Parameter("cd", CD.class, cd, null));
        call.setParams(params);

        // Aufrufen
        Response response;
        response = call.invoke(url, "");

        if (!response.generatedFault(  )) {
            System.out.println("CD erfolgreich hinzugefügt.");
        } else {
            Fault fault = response.getFault(  );
            System.out.println("Fehler: " + fault.getFaultString(  ));
        }
    }

    public static void main(String[] args) {
        if (args.length != 4) {
            System.out.println("Benutzung: java javaxml2.CDAdder [SOAP-Server-URL] " +
                "\"[CD-Titel]\" \"[Künstlername]\" \"[Plattenfirma]\"");
            return;
        }

        try {
            // URL des zu benutzenden  SOAP-Servers
            URL url = new URL(args[0]);

            // Holen der Werte für eine neue CD
            String title = args[1];
            String artist = args[2];
            String label = args[3];

            // Hinzufügen der CD
            CDAdder adder = new CDAdder(  );
            adder.add(url, title, artist, label);
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Die einzige interessante Sache ist der Umgang mit dem Mapping der Klasse CD:

        // Mapping des Typs, so daß SOAP ihn benutzen kann
        SOAPMappingRegistry registry = new SOAPMappingRegistry(  );
        BeanSerializer serializer = new BeanSerializer(  );
        registry.mapTypes(Constants.NS_URI_SOAP_ENC,
            new QName("urn:cd-catalog-demo", "cd"),
            CD.class, serializer, serializer);   

So wird ein benutzerdefinierter Typ codiert und über das Netz verschickt. Ich habe bereits angesprochen, wie die Klasse BeanSerializer benutzt werden kann, um Typen im JavaBean-Format wie die Klasse CD zu behandeln. Um das dem Server beizubringen, habe ich den Deployment-Deskriptor benutzt, jetzt muß man dem Client noch erklären, wie er den Serializer/Deserializer benutzen soll. Das wird durch die Benutzung der Klasse SOAPMappingRegistry erreicht. Die Methode mapTypes( ) erwartet einen String, der die Codierung angibt (die Benutzung von NS_URI_SOAP_ENC ist auch hier wieder die beste Idee), und Informationen über den Parametertyp, für den eine spezielle Serialisierung benutzt werden soll.

Zunächst wird ein QName angegeben. Das war der Grund für den komischen Namensraum im Deployment-Deskriptor: Sie müssen hier exakt dieselbe URN angeben, wie auch den lokalen Namen des Elements (in diesem Fall »CD«), anschließend das Java-Class-Objekt der zu serialisierenden Klasse (CD.class) und schließlich die Klasseninstanz für Serialisierung und Deserialisierung. Im Falle von BeanSerializer funktioniert eine Instanz für beides. Wenn all das in der Registry eingetragen ist, müssen Sie das Call-Objekt über einen Aufruf der Methode setSOAPMapping-Registry( ) noch davon in Kenntnis setzen.

Sie können dieses Beispiel wie schon zuvor starten. Fügen Sie noch eine Plattenfirma hinzu, dann sollte es keine Probleme geben:

C:\javaxml2\build>java javaxml2.CDAdder 
    http://localhost:8080/soap/servlet/rpcrouter 
    "Tony Rice" "Manzanita" "Sugar Hill"
Füge CD mit dem Titel 'Tony Rice' von 'Manzanita' hinzu, erschienen auf dem Label Sugar Hill
CD erfolgreich hinzugefügt.

Ich überlasse es Ihnen, die Klasse CDLister entsprechend zu ändern. Die Beispiele, die Sie herunterladen können, enthalten die modifizierte Version ebenfalls.

Vielleicht denken Sie, daß es unnötig ist, hier Änderungen vorzunehmen, da die Klasse CDLister nicht direkt mit Objekten der Klasse CD arbeitet (der Rückgabewert der Methode list( ) ist vom Typ Hashtable). Die zurückgegebene Hashtable enthält jedoch Instanzen der Klasse CD. Wenn SOAP nicht weiß, wie es diese deserialisieren soll, wird der Client einen Fehler melden. Daher muß eine Instanz der Klasse SOAPMappingRegistry an die Instanz der Klasse Call übergeben werden, damit es funktioniert.

Bessere Fehlerbehandlung

Nachdem Sie nun benutzerdefinierte Objekte herumwerfen, RPC-Aufrufe durchziehen und alle anderen im Büro alt aussehen lassen, möchte ich über ein nicht so aufregendes Thema sprechen: die Fehlerbehandlung. In jeder Netzwerktransaktion können viele Dinge schiefgehen. Der Service läuft nicht, ein Fehler tritt auf dem Server auf, Objekte können nicht gefunden werden, Klassen werden vermißt, und eine ganze Menge anderer Probleme ist möglich. Bisher benutzte ich nur die Methode fault.getString( ), um Fehler anzuzeigen. Dieses Vorgehen ist nicht immer hilfreich. Um das nachzuvollziehen, müssen Sie folgende Zeile im Konstruktor der Klasse CDCatalog auskommentieren:

    public CDCatalog(  ) {
        //catalog = new Hashtable(  );

        // Bestücken des Katalogs
        addCD(new CD("Nickel Creek", "Nickel Creek", "Sugar Hill"));
        addCD(new CD("Let it Fall", "Sean Watkins", "Sugar Hill"));
        addCD(new CD("Aerial Boundaries", "Michael Hedges", "Windham Hill"));
        addCD(new CD("Taproot", "Michael Hedges", "Windham Hill"));
    }

Kompilieren Sie das, führen Sie eine erneute Installation durch, und starten Sie die Servlet-Engine neu. Das Ergebnis ist eine NullPointerException, wenn die Klasse versucht, eine Instanz der Klasse CD zu der uninitialisierten Hashtable hinzuzufügen. Wenn Sie den Client jetzt starten, wird er einen Fehler melden, allerdings können Sie aus der Art, wie er das tut, nicht allzuviel entnehmen:

(gandalf)/javaxml2/build$ java javaxml2.CDLister 
    http://localhost:8080/soap/servlet/rpcrouter
Auflistung des Inhalts des CD-Katalogs.
Fehler: Unable to resolve target object: null

Das ist nicht genau die Art von Information, die Ihnen hilft, das Problem zu finden. Jedoch ist ja das Framework da, das eine bessere Arbeit bei der Fehlerbehandlung leistet; erinnern Sie sich an den DOMFaultListener, den wir im Element faultListener spezifiziert haben? Hier kommt er nun zum Einsatz. Das im Fehlerfall zurückgegebene Fault-Objekt enthält ein DOM-org.w3c.dom.Element mit detaillierten Fehlerinformationen. Zunächst wird eine import-Anweisung für die Klasse java.util.Iterator in den Quell-Code des Clients eingefügt:

import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.apache.soap.Constants;
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.util.xml.QName;

Als nächstes ändern Sie in der Methode list() die Art der Fehlerbehandlung:

        if (!response.generatedFault(  )) {
            Parameter returnValue = response.getReturnValue(  );
            Hashtable catalog = (Hashtable)returnValue.getValue(  );
            Enumeration e = catalog.keys(  );
            while (e.hasMoreElements(  )) {
                String title = (String)e.nextElement(  );
                CD cd = (CD)catalog.get(title);
                System.out.println("  '" + cd.getTitle(  ) + "' von " + 
                    cd.getArtist( ) +
                    " bei der Plattenfirma " + cd.getLabel(  ));
            }
        } else {
            Fault fault = response.getFault(  );
            System.out.println("Fehler: " + fault.getFaultString(  ));

            Vector entries = fault.getDetailEntries(  );
            for (Iterator i = entries.iterator(); i.hasNext(  ); ) {
                org.w3c.dom.Element entry = (org.w3c.dom.Element)i.next(  );
                System.out.println(entry.getFirstChild().getNodeValue(  ));
            }
        }

Durch die Benutzung der Methode getDetailEntries( ) erhalten Sie Zugriff auf die rohen Daten, die vom SOAP-Server und dem Service über den Fehler zur Verfügung gestellt werden. Der Code iteriert durch diese Daten (dabei handelt es sich normalerweise nur um ein Element, aber man weiß ja nie...) und holt das DOM-Element, das in jedem Eintrag enthalten ist. Hier ist das XML, das durchgearbeitet wird:

<SOAP-ENV:Fault>
  <faultcode>SOAP-ENV:Server.BadTargetObjectURI</faultcode>
  <faultstring>Unable to resolve target object: null</faultstring>
  <stacktrace>Das ist, was wir wollen!</stacktrace>
</SOAP-ENV:Fault>

Mit anderen Worten: Sie erhalten über das Fault-Objekt Zugriff auf den Teil des SOAP-Envelopes, der sich mit Fehlern befaßt. Zusätzlich bietet Apache SOAP, wenn Fehler auftreten, einen Stacktrace, der detaillierte Informationen zur Fehlerdiagnose liefert. Durch Zugriff auf das Element stackTrace und durch die Ausgabe des Wertes im Knoten Text dieses Elements gibt der Client den Stacktrace des Servers aus. Kompilieren Sie die Änderungen, und starten Sie den Client erneut. Das sollte zu folgender Ausgabe führen:

C:\javaxml2\build>java javaxml2.CDLister http://localhost:8080/soap/servlet/rpcr
outer
Auflistung des Inhalts des CD-Katalogs.
Fehler: Unable to resolve target object: null
java.lang.NullPointerException
        at javaxml2.CDCatalog.addCD(CDCatalog.java:24)
        at javaxml2.CDCatalog.<init>(CDCatalog.java:14)
        at java.lang.Class.newInstance0(Native Method)
        at java.lang.Class.newInstance(Class.java:237)

Das geht noch ein Stück weiter, aber die wichtigen Informationen einschließlich der, daß eine NullPointerException auftrat, sind zu sehen. Sie können sogar die Nummer der Zeile in der Klasse auf dem Server sehen, in der der Fehler auftrat. Das Resultat dieser eher kleinen Änderung am Quell-Code ist ein viel robusteres Mittel zur Behandlung von Fehlern. Das sollte Sie darauf vorbereiten, Fehler in Ihren Server-Klassen zu bereinigen. Und übrigens: Ändern Sie Ihre Klasse CDCatalog wieder so, daß diese Fehler verschwunden sind, bevor Sie fortfahren!

Und was kommt jetzt?

Das nächste Kapitel stellt eine direkte Weiterentwicklung der hier besprochenen Themen dar. Mehr und mehr wird XML zu einem Eckpfeiler von Business-to-Business-Aktivitäten, und SOAP ist der Schlüssel dazu. Im nächsten Kapitel werde ich zwei wichtige Technologien vorstellen: UDDI und WSDL. Wenn Sie keine Idee haben, worum es sich dabei handeln könnte, sind Sie hier genau richtig. Sie werden sehen, wie diese beiden Technologien zusammen in der Lage sind, das Rückgrat einer Web-Services-Architektur zu bilden. Bereiten Sie sich darauf vor herauszufinden, was an diesem Web-Services- und Peer-to-Peer-Hype wirklich dran ist.

1)
Es wird viel darüber gesprochen, SOAP über andere Protokolle laufen zu lassen, wie zum Beispiel SMTP (oder sogar Jabber). Das ist nicht Teil des SOAP-Standards, könnte aber in zukünftigen Versionen enthalten sein. Seien Sie nicht überrascht,wenn Sie darüber lesen.
2)
Sie können über das Bean Scripting Framework auch Skripten benutzen, aber aus Platzgründen wird das hier ausgespart. Zu diesem Themenkreis sehen Sie sich bitte das O’Reilly-SOAP-Buch oder die Dokumentation unter http://xml.apache.org/soap an, wo Sie mehr Details zur Skriptunterstützung in SOAP finden können.