SOAP mit Java 6 und Apache CXF – Umgang mit Änderungen in der WSDL

1. Einleitung

Mit zunehmender Nachfrage nach Interoperabilität diverser verteilter Systeme im Unternehmensumfeld wächst auch die Nachfrage nach standardisierten Schnittstellen.
So bietet beispielsweise eine Firma (XY GmbH) ihren zahlreichen Vertriebspartnern eine Webservice-Schnittstelle (SOAP) an, die sich optimal in bereits existierende Warenwirtschaftssysteme integrierten lässt und einen sicheren Datenaustausch auf Grundlage eines etablierten Standards (XML) ermöglicht.
Alle Nachrichten, die dabei zwischen Client und Server ausgetauscht werden, basieren auf einer öffentlichen Definition dieser Schnittstelle (WSDL).
Um den reibungslosen Ablauf zu gewährleisten, sollen Nutzer dieser Schnittstelle möglichst wenig von evolutionären Änderungen dieser Definition betroffen sein. Gleichzeitig sollen jedoch alle Änderungen für alle Nutzer wirksam werden und alle Nutzer sollen die gleiche generische Schnittstelle benutzen.
Die Definition der Schnittstelle sowie der Verarbeitungsprozess für eingehende Nachrichten (Serverseite) sollen so dynamisiert werden, dass den einzelnen Service-Methoden beliebig Parameter hinzugefügt werden können, ohne dabei Nachrichten existierender Nutzer ungültig zu machen.
Diese Anforderungen werfen die Frage auf, wie in Zukunft mit Nachrichten verfahren werden soll, die auf Grundlage einer veralteten Definition erstellt wurden.
Ziel dieser Arbeit ist es, eine Lösung zu finden, wie solche Änderungen an der WSDL-Datei abwärtskompatibel gestaltet werden können.

2. Einführung in XML

XML [(Extensible Markup Language)] ist zunächst nichts anderes als ein einfaches, wohldefiniertes Format einer Textdatei. [1] Anfang 1998 wurde [es] zum offiziellen Standard des W3-Konsortiums erhoben […]. [1]
Eine Fähigkeit von XML [im Unterschied zu HTML] ist es, „eigene Tags und Attribute selbst definieren und nutzen können“. [2]
Durch eine genaue Spezifizierung von XML hat man […] ein präzises Dateiformat in den Händen, das immer den XML-Regeln gehorchen muss. [3] Dadurch ist es möglich, plattformunabhängig Daten auszutauschen. [3]

2.1. Einfacher Aufbau

Der Aufbau (Tags und Attribute) eines XML-Dokuments lässt sich durch eine DTD (Document Type Definition) spezifizieren. Ein XML-Dokument gilt als wohlgeformt, wenn es den Regeln der DTD genügt.
Beispiel eines einfachen XML-Dokuments: [4]

<?xml version="1.0" encoding="UTF-8"?>
<element name="TradePriceResponse">
	<complexType>
		<all>
			<element name="price" type="integer" />
		</all>
	</complexType>
</element>

2.2. Komplexer Aufbau (Namensräume)

Namensräume kommen in XML […] sehr häufig vor. [5] [Sie] sind vom W3-Konsortium als Standard verabschiedet und ermöglichen es, dass es nicht zu Zweideutigkeiten bei XML-Elementen kommen kann, die unterschiedlich verwendet werden. [5]
Namensräume sollen sicherstellen, „dass Elemente mit gleichem Namen vorkommen können, ohne dass es zu Konflikten kommt“. [6] Der grundlegende Trick: Die Tags werden für einen bestimmten Raum definiert, in dem sie gültig sind. [5]
Dies ist der so genannte Namensraum. Jedes Tag muss dann […] mit der Information versehen werden, für welchen Namensraum es gültig ist. [7]
Beispiel eines komplexen XML-Dokuments:

<?xml version="1.0" encoding="UTF-8"?>
<element name="TradePriceResponse" xmlns:elem="http://www.w3.org/2000/10/XMLSchema">
	<elem:complexType>
		<elem:all>
			<elem:element name="price" type="integer" />
		</elem:all>
	</elem:complexType>
</element>

Jedem Element unterhalb von „element“ wurde ein so genanntes Präfix zugeordnet (hier: elem). Das Präfix zusammen mit dem Namen des Elements [(z.B. elem:complexType)] wird als so genannter qualifizierter Name bezeichnet. [8]
Über das Attribut „xmlns“ wurde die Definition des Namensraums eingeleitet, indem eine URI angegeben wurde. Der [gesamte] Namensraum setzt sich aus dem definierten Präfix und der zugehörigen URI zusammen. Beide müssen immer miteinander in Verbindung stehen. [9]

3. Nachrichtenaustausch via SOAP

Der Webservice der XY GmbH basiert auf SOAP.
SOAP ist ein einfaches XML basiertes Protokoll, das über HTTP einen Nachrichtenaustausch zwischen Applikationen ermöglicht. [10] Jeder an der Kommunikation Beteiligte kennt diesen Standard und ist damit mit sehr einfachen Mitteln in der Lage, sich an der Kommunikation zu beteiligen. [11]



Lebenszyklus eines SOAP-Requests
[12]

Abbildung 1: Lebenszyklus eines SOAP-Requests

3.1. Motivation zur Verwendung von SOAP

HTTP wird von allen Internetbrowsern und Servern unterstützt. Zudem umgeht man Blockaden durch Firewalls oder Proxyserver. Desweiteren bietet SOAP die Möglichkeit der Kommunikation zwischen Applikationen, die auf unterschiedlichen Plattformen laufen. [13]
Daher fiel die Entscheidung bei der zu verwendenden Technologie bei der XY GmbH zugunsten von SOAP aus.

3.2. Vom Client zum Server

Ein Problem bei einer solchen Kommunikation [per SOAP] ist die Frage, wie der Name der angebotenen Servicemethode lautet, welche und wie viele Parameter sie erwartet und was ihr Rückgabewert ist. [11] Alle diese Informationen werden von einem Webservice wieder in einer standardisierten XML-Datei vorgehalten, der WSDL (Web Service Description Language). Diese „kann von einem Computerprogramm interpretiert und damit die passende SOAP-Nachricht automatisch aufgebaut und verarbeitet werden“. [14]
Soll ein Webservice aufgerufen werden, erzeugt der Client eine SOAP-Nachricht, in der der Name der aufzurufenden Methode und ihre Parameter in Klartext enthalten sind. [15]
Der Server interpretiert die SOAP-Nachricht, ruft die gewünschte Methode mit den übergebenen Parametern auf und liefert das Ergebnis wieder als SOAP-Nachricht an den Client zurück. [15]
Für den Aufbau solcher Nachrichten „gibt es fertige Java-Bibliotheken wie JAX-RPC und JAX-WS“. [16] Seit Java 6 wird mit wsimport im JDK bereits ein Tool bereit gestellt, „mit dem JAX-WS-Artefakte wie z.B. das Service Endpoint Interface (SEI) oder Exceptions generiert werden können“. [17]

3.3. Aufbau von SOAP-Nachrichten

Eine SOAP-Nachricht ist ein einfaches XML-Dokument, das aus folgenden Elementen besteht: [18]
Envelope-Element
Durch dieses Element wird das XML-Dokument als SOAP-Nachricht identifiziert.
Header-Element
Es enthält Header-Informationen.
Body-Element
Dieses Element enthält Informationen darüber, ob es sich um einen Aufruf (Call) oder eine Antwort (Response) handelt.
Content-/Fault-Element
In diesem Element werden die eigentlichen Nutzdaten bzw. Status- und Fehlerinformationen abgelegt.



Aufbau einer SOAP-Nachricht
[19]

Abbildung 2: Aufbau einer SOAP-Nachricht


SOAP-Nachrichten bieten zudem die Möglichkeit, Anhänge zu übertragen. Dabei kann es sich z.B. um Base64-kodierte Binärdateien wie bspw. PDF-Dateien handeln. Diese Informationen werden jeweils in einem Attachment-Element abgelegt.
Jede SOAP-Nachricht wird auf Grundlage der auf dem Server hinterlegen Webservice-Beschreibung (WSDL-Datei) erstellt.
Sind die Java-Klassen zur Kommunikation einmal erstellt (z.B. mittels wsimport), erzeugt der Client immer Nachrichten nach dem gleichen Muster. Dabei können Änderungen in der Service-Beschreibung (WSDL-Datei) dazu führen, dass diese Nachrichten nicht mehr interpretiert werden können. Dies würde einen Fehler verursachen.
Es gilt also, die WSDL-Datei nur in dem Maße zu verändern, dass Clients in ihrer Kommunikation nicht beeinträchtigt werden. Änderungen in der WSDL dürfen beim Client nicht zu einem Fehler führen. Das heißt, alle Änderungen müssen so implementiert werden, dass sie abwärtskompatibel sind.

4. Kategorien von WSDL-Änderungen [4]

Grob gesagt gibt es zwei Arten von Änderungen in einem WSDL-Dokument; jene die die Kommunikation existierender Clients nicht beeinträchtigen (abwärtskompatibel) und jene die sie beeinträchtigen (nicht abwärtskompatibel).
Zu den abwärtskompatiblen Änderungen gehören:

  • Hinzufügen neuer Service-Methoden zu einem existierenden WSDL-Dokument
  • Hinzufügen neuer XML-Schema-Typen zu einem existierenden WSDL-Dokument (solange diese nicht bereits in einem anderen Typ enthalten waren)

Zu den nicht abwärtskompatiblen Änderungen gehören:

  • Löschen einer Service-Methode
  • Umbenennen einer Service-Methode
  • Änderungen der Parameter (Datentyp oder Reihenfolge) einer Methode
  • Änderungen an der Struktur eines komplexen Datentyps

5. Best Practices

Im weitesten Sinne gibt es zwei unterschiedliche Strategien, die verschiedenen Arten von WSDL-Änderungen zu behandeln. [4]
Für abwärtskompatible Änderungen kann einfach das aktuelle WSDL-Dokument durch eine aktualisierte Version ersetzt werden. [4]
Für nicht abwärtskompatible Änderungen verhält es sich etwas anders. [4] IBM schlägt an dieser Stelle die Verwendung von XML-Namensräumen vor. Dabei wird mit jeder SOAP-Nachricht (ein- und ausgehend) ein spezifischer Namensraum-Wert mit gesendet. [4] Dieses Vorgehen ermöglicht es dem implementierten Webservice anhand des Namensraum-Wertes korrekt herauszufinden, was genau mit der eintreffenden Nachricht zu tun ist. [4]

5.1. Kritische Betrachtung

Für eingangs genanntes Problem der Änderungen in der Parametersignatur der Service-Methoden müsste bei diesem Vorgehen für jede Änderung eine neue Methode mit gleichem Namen und eindeutigem Namensraum sowie einer geänderten Parameterliste in die WSDL-Datei aufgenommen werden. Dadurch wird die WSDL-Datei schnell groß und unübersichtlich.
Ein anderer Ansatz ist die Behandlung laut Definition ungültiger Nachrichten, die beim Server eintreffen. Hierbei sollen die eintreffenden Nachrichten validiert und ggf. um fehlende Werte erweitert werden.
Ein entsprechendes Entwurfsmuster für dieses Vorgehen existiert bereits.

6. Entwurfsmuster: Interceptor (dt. Abfänger)

„Entwurfsmuster (engl. design patterns) sind bewährte Lösungs-Schablonen für wiederkehrende Entwurfsprobleme in [der] Softwarearchitektur und Softwareentwicklung“. [20] Sie fassen Design- und Architekturwissen in kompakter und wiederverwertbarer Form zusammen. [21]
Das Interceptor-Pattern wird benutzt, „wenn Anwendungssysteme oder Frameworks ihren gewöhnlichen Arbeitsprozess ändern sollen“. [22] […] Schlüsselaspekt dieses Patterns ist es, dass diese Änderungen transparent und automatisch geschehen. [22] Der Rest des Systems braucht nicht zu wissen, dass etwas hinzugefügt oder geändert wurde und soll weiterarbeiten wie zuvor. [22]
Auch laut IBM „ist das Interceptor-Pattern das Effektivste für die Validierung von Parametern, bevor eine Transaktion ausgeführt wird“. [23]
Zudem bilden Interceptoren in dem verwendeten Framework Apache CXF „die fundamentale Einheit“. [24] Daher dient dieses Entwurfsmuster als Lösungsansatz für das genannte Problem der Änderungen in der Service-Beschreibung (WSDL-Datei).
Hierbei sollen eintreffende SOAP-Nachrichten auf der Serverseite abgefangen und schon vor deren Interpretation an die veränderte WSDL-Datei angepasst werden. Das heißt, dass fehlende Parameter beim Aufruf einer Webservice-Methode durch definierte Standardwerte ersetzt werden sollen.
Danach soll der Aufruf normal bearbeitet werden, als wären von vornherein alle Parameter enthalten gewesen.

6.1. Interceptoren und Phasen

„Beim Ausführen eines Service wird eine Interceptor-Kette erzeugt und ausgeführt. Dabei hat jeder Interceptor die Möglichkeit, die SOAP-Nachricht u.a. zu lesen oder zu transformieren“. [24]
Ruft ein Client eine Servicemethode auf dem Server auf, „gibt es eine ausgehende Interceptor-Kette für den Client und eine eingehende Kette für den Server“. [24]
Abbildung 3 stellt den Ablauf einer solchen Nachrichtenverarbeitung schematisch dar.



Verarbeitung der XML-Nachrichten
[23]

Abbildung 3: Verarbeitung der XML-Nachrichten


Wenn der Server dem Client antwortet, „gibt es eine ausgehende Kette für den Server und eine Eingehende für den Client“. [24]
Den Einstiegspunkt, wann eine Nachricht vom Interceptor verarbeitet werden soll, definiert man bei Apache CXF über die Angabe einer so genannten Phase. Dadurch kann der Interceptor an einer bestimmten Stelle in der jeweiligen Kette eingehängt werden.

6.1.1. Implementierung

Da für die o.g. Problemstellung nur eintreffende Nachrichten auf ihre Gültigkeit hin überprüft und ggf. angepasst werden müssen, braucht es einen entsprechenden Interceptor auch nur in der eingehenden Interceptor-Kette.
Der erstellte Interceptor heißt OptionalParameterInInterceptor (OPII) und wurde auf Basis des in der Apache CXF Bibliothek enthaltenen AbstractPhaseInterceptor erstellt.
Unter Verwendung der Phase POST_STREAM wird der OPII an die Stelle der eingehenden Interceptor-Kette eingehängt, an der die XML-Nachricht auf dem Server zur Verarbeitung bereit steht.
Die durch die abstrakte Klasse AbstractPhaseInterceptor vorgegebene Methode handleMessage(SoapMessage) wird während der Abarbeitung der Interceptor-Kette ausgeführt.
Mittels der in der XML-Nachricht enthaltenen Header-Informationen wird zunächst ermittelt, welcher Webservice diese Nachricht verarbeiten soll. Über den eindeutigen Namen des Services erhält man dann eine Referenz auf die Klasse des der WSDL-Datei zugrunde liegenden Interfaces. Diese wird zur Ermittlung der auszuführenden Service-Methode benötigt.
Da SOAP kein Überladen von Methoden vorsieht, ist der Name einer Methode eindeutig und kann zur Identifizierung der Service-Methode verwendet werden. Mit Hilfe von Java Reflections wird nun die Methode mit dem in der eingehenden XML-Nachricht enthaltenen Methodennamen ermittelt.
An diesem Punkt braucht es einen Weg herauszufinden, ob die auszuführende Methode optionale Parameter überhaupt zulässt. Dabei kommt bei mit Annotationen versehenen Methoden ein seit Java 5 bewährtes Konzept zum Einsatz.
Eine Service-Methode, die zusätzliche Parameter enthält und damit die WSDL-Datei beeinflusst, wird mit der Annotation @Optional versehen, welche ein Array vom Typ @Parameter beinhaltet. Objekte vom Typ @Parameter wiederum kapseln den Namen des betreffenden Parameters und einen Standardwert. Den Zusammenhang der beiden Annotationen stellt das in Abbildung 4 gezeigte Klassendiagramm dar.
Die folgende Abbildung zeigt beispielhaft die Verwendung der Annotationen im Java-Quelltext.

@Optional(params = {
   @Parameter(partName = "foo", defaultValue = "0")
})

Im OPII werden nun diese Annotationen genutzt um evtl. fehlende Parameter zu ergänzen.
Dazu wird zunächst die eingetroffene XML-Nachricht zu einem org.w3c.dom.Document geparst. Diesem Dokument lassen sich nun unter Verwendung einer standardisierten API Elemente hinzufügen.



Klassendiagramm @Optional - @Parameter

Abbildung 4: Klassendiagramm @Optional - @Parameter


Die manipulierte XML-Nachricht wird anschließend an den nächsten Interceptor in der eingehenden Kette übergeben. Am Ende der Kette wird dann die Nachricht verarbeitet, als wäre sie bereits mit einer korrekten Signatur eingetroffen.


EPK - XML-Verarbeitung im OPII

Abbildung 5: EPK - XML-Verarbeitung im OPII


Abbildung 5 zeigt den Prozess der XML-Verarbeitung innerhalb des OPII in Form einer ereignisgesteuerten Prozesskette (EPK).

6.1.2. Praxisbeispiel

Es sei folgende Situation gegeben:
Es wird ein fiktiver Webservice (SOAP) angeboten, der eine Methode bereitstellt, die eine RSS-XML-Datei erstellt und diese als Antwort an den Nutzer zurück sendet. In dieser XML-Datei sollen Feed-Einträge aus einem bestimmten Zeitraum enthalten sein.
Zu diesen Zweck müssen der Service-Methode zwei Parameter übergeben werden: Ein Anfangs- und ein Enddatum.
Die folgende Abbildung stellt den Aufbau der WSDL für den o.g. Webservice schematisch dar.



Schematischer Aufbau der WSDL

Abbildung 6: Schematischer Aufbau der WSDL


Nach dem Generieren aller zur Nutzung dieses Webservices notwendigen Klassen/Interfaces werden anschließend vom Client immer Nachrichten im folgenden Format zum Server gesendet.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<tns:getRssFeed xmlns:tns="external:soap.wieczorek-it.de">
			<from>01.01.2010 00:00:00 CET</from>
			<to>17.02.2010 23:59:59 CET</to>
		</tns:getRssFeed>
	</soap:Body>
</soap:Envelope>

Erhält der Server diese SOAP-Nachricht, generiert er eine RSS-XML-Datei mit allen Einträgen vom 01.01.2010 bis zum 17.02.2010 und sendet diese Base64-kodiert an den Client zurück.
Im Zuge der Weiterentwicklung dieses Webservices soll in Zukunft neben dem Anfangs- und Enddatum auch eine Kategorie angegeben werden, um nicht immer alle Feeds aller Kategorien zurückzuliefern.
Zu diesem Zweck wird nun die WSDL entsprechend angepasst, sodass der Methode „getRssFeed“ insgesamt drei Parameter übergeben werden müssen. Ein schematischer Aufbau der neuen WSDL ist in der folgenden Abbildung dargestellt.



Schematischer Aufbau der WSDL (2)

Abbildung 7: Schematischer Aufbau der WSDL (2)


Clients, die jetzt auf Basis der vorherigen WSDL generiert wurden, würden nun stets ungültige Nachrichten an den Server senden und eine entsprechende Fehlermeldung erhalten.
Um dieses Problem zu umgehen, ohne dabei alle Nutzer zum erneuten Generieren ihrer Clients zu nötigen, wird der neu hinzugekommene Parameter „category“ serverseitig als optional gekennzeichnet (unter Verwendung der o.g. Annotationen und des OPII) und mit dem Standardwert „all“ versehen.
Ist dieser Parameter in einer eintreffenden SOAP-Nachricht nicht enthalten, wird sie um diesen definierten Standardwert erweitert. Auf diese Weise werden „alten“ Clients weiterhin alle Feeds aller Kategorien zurückgeliefert. Clients, die auf Grundlage der überarbeiteten WSDL erstellt wurden können hingegen beeinflussen, ob sie alle Feeds oder nur diejenigen einer bestimmten Kategorie zurückgeliefert bekommen.

7. Grenzen der Implementierung

Die aufgezeigte Lösung behandelt lediglich Änderungen in der Methodensignatur von Webservice-Methoden. Es werden fehlende Parameter in der übergebenen Liste erkannt und ggf. Standardwerte eingefügt.
Damit ist eine der vier o.g. nicht abwärtskompatiblen Änderungen der WSDL zu einer abwärtskompatiblen Änderung geworden.
Jedoch ließen sich über den Interceptor-Ansatz alle vier möglichen Änderungen in abwärtskompatible Änderungen transferieren. So ließe sich beispielsweise ein weiterer Interceptor implementieren, der Alias-Namen von Service-Methoden auswertet und damit das Problem der Methodenumbenennung behebt.

8. Fazit

Das Ziel, eine Lösung zu finden, wie Änderungen an der WSDL-Datei abwärtskompatibel gestaltet werden können, wurde erreicht.
Der Lösungsansatz mit Interceptoren wurde für neu hinzugefügte Parameter einer Webservice-Methode implementiert.
Insgesamt ist zu sagen, dass es schwierig ist, eine solche Dynamik in die Definition eines Webservice zu bringen. Hier treffen unterschiedliche Anforderungen aufeinander: Einerseits eine gewünschte Kontinuität seitens der Nutzer eines Webservice, andererseits die evolutionären Änderungen in den Prozessabläufen seitens des Service-Anbieters. Hierbei spielt es keine Rolle, nach welcher Vorgehensweise man sich dabei richtet: Ob Versionierung der WSDL (wie von IBM vorgeschlagen) oder den Einsatz von Interceptoren (wie in dieser Arbeit gezeigt).
In jedem Fall muss der Entwickler eines solchen Service die entsprechende Lösung selbst implementieren, „da es [bisher] in der Webservice-Architektur keine integrierte Lösung gibt“. [4]

Anhang

Quelltext: OptionalParameterInInterceptor.java

package de.wieczorek-it.soap.interceptors;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.BindingProvider;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.StaxInInterceptor;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import de.wieczorek-it.soap.interceptors.Optional.Parameter;
import de.wieczorek-it.soap.tools.PortType;

/**
 * This interceptor handles optional parameters in SOAP messages. It allows clients that where build using an older
 * version of the WSDL file to call modified methods with an 'incomplete' parameter list.<br/>
 * These methods have to be annotated with {@link Optional}.<br/>
 * If such a method was called the SOAP message may be modified setting the default values for the missing parameters.
 * 
 * @author Marcel Wieczorek
 * @version 1.0
 */
public class OptionalParameterInInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
    private static final Logger LOG = LogUtils.getL7dLogger(OptionalParameterInInterceptor.class);

    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
    private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();

    /** pattern to find body element <code>((.*:)*BODY)</code> */
    private static final Pattern BODY_PATTERN = Pattern.compile("((.*:)*BODY)");
    /** pattern to replace method prefix <code>(.*:)*</code> */
    private static final Pattern METHOD_REPLACEMENT_PATTERN = Pattern.compile("(.*:)*");

    public OptionalParameterInInterceptor() {
		super(Phase.POST_STREAM);
		addBefore(StaxInInterceptor.class.getName());
    }

    @Override
    public void handleMessage(final SoapMessage message) {
		// check whether message comes via POST
		if (isGET(message) || message.getContentFormats().contains(XMLStreamReader.class)) {
		    LOG.info("StaxInInterceptor skipped.");
		    return;
		}
	
		try {
		    final Exchange exchange = message.getExchange();
		    final Endpoint endpoint = exchange.get(Endpoint.class);
		    final Service service = endpoint.getService();
		    final QName serviceName = service.getName();
		    
		    // find defining service interface
		    final PortType pt = PortType.getByServiceName(serviceName);
		    if (pt != null) {
				final Class<BindingProvider> portClass = pt.getPortClass();
				// read xml message
				final InputStream is = message.getContent(InputStream.class);
				final byte[] binary = IOUtils.readBytesFromStream(is);
		
				// build w3c document to manipulate xml message
				final InputStream xmlInputStream = new ByteArrayInputStream(binary);
				final DocumentBuilder builder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
				final Document document = builder.parse(xmlInputStream);
				final Element bodyElement = getBodyElement(document);
				if (bodyElement != null) {
				    final Element methodElement = (Element) bodyElement.getFirstChild();
				    final String methodElementName = methodElement.getNodeName().replaceAll(METHOD_REPLACEMENT_PATTERN.pattern(), "");
				    
				    // get method from interface to process optional parameters
				    final Method method = getMethodFromPortClass(portClass, methodElementName);
				    if (method != null) {
						final Optional optional = method.getAnnotation(Optional.class);
						if (optional != null) {
						    final Parameter[] params = optional.params();
						    if (params.length > 0) {
								boolean hasChanged = false;
								for (final Parameter p : params) {
								    final NodeList partList = document.getElementsByTagName(p.partName());
								    Element part = null;
								    if (partList != null && partList.getLength() == 1)
										part = (Element) partList.item(0);
								    else {
										// manipulation step 1: append new node
										part = document.createElement(p.partName());
										methodElement.appendChild(part);
										LOG.info("created new method parameter: " + p.partName());
								    }
								    
								    final String partText = part.getTextContent();
								    if (partText == null || partText.isEmpty()) {
										// manipulation step 1: set default value
										part.setTextContent(p.defaultValue());
										LOG.info("set " + p.defaultValue() + " to " + p.partName());
									if (!hasChanged)
									    hasChanged = true;
								    }
								}
								
								if (hasChanged) {
								    // set manipulated content
								    final Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
								    final ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream();
								    final Result result = new StreamResult(xmlOutputStream);
								    transformer.transform(new DOMSource(document), result);
								    final byte[] outputData = xmlOutputStream.toByteArray();
								    
								    LOG.info("manipulated message is now: " + new String(outputData));
								    
								    message.setContent(InputStream.class, new ByteArrayInputStream(outputData));
								    return;
								}
						    }
						}
				    }
				}
		
				// if no manipulation took place, set the original content
				message.setContent(InputStream.class, new ByteArrayInputStream(binary));
		    }
		} catch (Exception e) {
		    throw new Fault(e);
		}
    }

    /**
     * Finds a method from a given class by name using reflection. Due to the non-existence of overloading in SOAP
     * services there must not be more than one method having the same name.
     * 
     * @param clazz
     *            the class to get the method from
     * @param name
     *            the name of the method to find
     * @return the method that was found by name; if no method was found null will be returned
     */
    private Method getMethodFromPortClass(final Class<BindingProvider> clazz, final String name) {
		final Method[] methods = clazz.getMethods();
		if (methods != null && methods.length > 0) {
		    for (final Method m : methods) {
			if (m.getName().equals(name))
			    return m;
		    }
		}
		return null;
    }

    /**
     * Finds the body element within the xml stream. Due to inconsistent message-creation-processes on different clients
     * the element has to be found using a pattern.
     * 
     * @param document
     *            the document to use
     * @return the body element; if no body element was found null will be returned
     */
    private Element getBodyElement(final Document document) {
		if (document != null) {
		    final NodeList elements = document.getElementsByTagName("*");
		    if (elements != null && elements.getLength() > 0) {
				for (int i = 0; i < elements.getLength(); i++) {
				    final Element e = (Element) elements.item(i);
				    final String tagName = e.getTagName().toUpperCase();
				    if (BODY_PATTERN.matcher(tagName).matches()) {
						return e;
					}
				}
		    }
		}
		return null;
    }
}

Literaturverzeichnis

Monographien

  • Rozanski, U. (2007): Enterprise JavaBeans 3.0 mit Eclipse und JBoss, 1. Aufl., Heidelberg 2007
  • Eilebrecht, K., Starke, G. (2007): Patterns kompakt Entwurfsmuster für effektive Software-Entwicklung, 2. Aufl., München 2007
  • Seeboerger-Weichselbaum, M. (2004): Das Einsteigerseminar XML, 4. Aufl., Landsberg 2004

Internetquellen

  • w3schools (2009): SOAP Tutorial. URL: http://www.w3schools.com/soap/default.asp, Abruf am 28.10.2009
  • w3schools (2009): SOAP Syntax. URL: http://www.w3schools.com/soap/soap_syntax.asp, Abruf am 29.10.2009
  • Sun Microsystems (2005): jax-ws: JAX-WS 2.0 ea3 — wsimport. URL: https://jax-ws.dev.java.net/jax-ws-ea3/docs/wsimport.html, Abruf am 02.11.2009
  • Sun Microsystems (2009): Overview of SAAJ – The Java EE 5 Tutorial. URL: http://java.sun.com/javaee/5/docs/tutorial/doc/bnbhg.html, Abruf am 28.01.2010
  • Wikipedia (2009): Entwurfsmuster. URL: http://de.wikipedia.org/wiki/Entwurfsmuster, Abruf am 02.11.2009
  • Wikipedia (2009): Interceptor pattern. URL: http://en.wikipedia.org/wiki/Interceptor_pattern, Abruf am 16.02.2010
  • IBM (2006): Architecture for high-volume SOA-based enterprise systems. URL: http://www.ibm.com/developerworks/webservices/library/ws-soa-hivol/index.html, Abruf am 04.11.2009
  • IBM (2004): Best practices for Web services versioning. URL: http://www.ibm.com/developerworks/webservices/library/ws-version/, Abruf am 15.02.2010
  • The Apache Software Foundation (2009): Apache CXF — Interceptors. URL: http://cwiki.apache.org/CXF20DOC/interceptors.html, Abruf am 02.11.2009
  • Microsoft (2005): SOAP Message Modification using SOAP Extensions. URL: http://msdn.microsoft.com/en-us/library/esw638yk(VS.80).aspx, Abruf am 16.02.2010

Fußnoten

  1. [1]Seeboerger-Weichselbaum, M. (2004) S.21
  2. [2]Vgl. Seeboerger-Weichselbaum, M. (2004) S.22
  3. [3]Seeboerger-Weichselbaum, M. (2004) S.25
  4. [4]Vgl. http://www.ibm.com/developerworks/webservices/library/ws-version/, Stand 15.02.2010
  5. [5]Seeboerger-Weichselbaum, M. (2004) S.113
  6. [6]Vgl. Seeboerger-Weichselbaum, M. (2004) S.113
  7. [7]Seeboerger-Weichselbaum, M. (2004) S.114
  8. [8]Seeboerger-Weichselbaum, M. (2004) S.115
  9. [9]Seeboerger-Weichselbaum, M. (2004) S.116
  10. [10]http://www.w3schools.com/soap/default.asp, Stand 28.10.2009
  11. [11]Rozanski, U. (2007) S.529
  12. [12]http://msdn.microsoft.com/en-us/library/esw638yk(VS.80).aspx, Stand 16.02.2010
  13. [13]http://www.w3schools.com/soap/soap_intro.asp, Stand 28.10.2009
  14. [14]Vgl. Rozanski, U. (2007) S.529
  15. [15]Rozanski, U. (2007) S.530
  16. [16]Vgl. Rozanski, U. (2007) S.531
  17. [17]Vgl. https://jax-ws.dev.java.net/jax-ws-ea3/docs/wsimport.html, Stand 02.11.2009
  18. [18]http://www.w3schools.com/soap/soap_syntax.asp, Stand 29.10.2009
  19. [19]http://java.sun.com/javaee/5/docs/tutorial/doc/bnbhg.html, Stand 28.01.2010
  20. [20]Vgl. http://de.wikipedia.org/wiki/Entwurfsmuster, Stand 02.11.2009
  21. [21]Eilebrecht, K., Starke, G. (2007) S. 1
  22. [22]Vgl. http://en.wikipedia.org/wiki/Interceptor_pattern, Stand 16.02.2010
  23. [23]Vgl. http://www.ibm.com/developerworks/webservices/library/ws-soa-hivol/index.html, Stand 04.11.2009
  24. [24]Vgl. http://cwiki.apache.org/CXF20DOC/interceptors.html, Stand 04.11.2009