In einem aktuellen Kundenprojekt hatten wir die Anforderung eine Message-Queue zu implementieren, die neben dem reinen Queuing eine garantierte Zustellung zum Zielsystem gewährleisten sollte. Es musste sichergestellt werden, dass auch bei Nichtverfügbarkeit des Zielsystems keine Nachrichten verloren gehen.
Da keine zusätzlichen Kosten für Softwarelizenzen anfallen sollten und ich das Rad nicht neu erfinden wollte, habe ich nach einer Lösung im OpenSource-Bereich gesucht. Mit Apache ActiveMQ bin ich fündig geworden. Innerhalb der verteilten Applikation wurde Axis2 verwendet. Das Zielsystem ist per SOAP/HTTP zu erreichen, während ActiveMQ eine Message-Queue auf JMS-Basis darstellt.
Um das Queuing zu implementieren, war folgende Reihenfolge der Nachrichtenübermittlung erforderlich:
Initial sah die Implementierung ziemlich trivial aus, allerdings ergaben sich im Projektverlauf einige Schwierigkeiten.
Da es für Axis2 kein Maven 1 Plugin gibt, bin ich für die Generierung der Stubklassen auf Maven 2 ausgewichen und habe dies in unseren Maven 1 Buildprozess integriert.
Der Service wird durch folgende WSDL beschrieben:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:types="http://jdi.communardo.de/swatesttypes/2008-01"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:ref="http://ws-i.org/profiles/basic/1.1/xsd" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://jdi.communardo.de/swatest/2008-1"
xmlns:tns="http://jdi.communardo.de/swatest/2008-1">
<wsdl:types>
<xsd:schema targetNamespace="http://ws-i.org/profiles/basic/1.1/xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:simpleType name="swaRef">
<xsd:restriction base="xsd:anyURI" />
</xsd:simpleType>
</xsd:schema>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ref="http://ws-i.org/profiles/basic/1.1/xsd" targetNamespace="http://jdi.communardo.de/swatesttypes/2008-01">
<xsd:import namespace="http://ws-i.org/profiles/basic/1.1/xsd" />
<xsd:complexType name="swatestMessageType">
<xsd:sequence>
<xsd:element name="requestId" type="xsd:string" />
<xsd:element name="clientNr" type="xsd:string" />
<xsd:element name="formFields"
type="types:formField" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="attachmentForm" type="ref:swaRef"
minOccurs="0" maxOccurs="1" />
<xsd:element name="fileName" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="formField">
<xsd:all>
<xsd:element name="name" type="xsd:string" />
<xsd:element name="value" type="xsd:string" />
</xsd:all>
</xsd:complexType>
<xsd:element name="newMessage" type="types:swatestMessageType" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="createNewMessageRequest">
<wsdl:part name="newMessageRequest" element="types:newMessage" />
<wsdl:part name="attachment" type="xsd:base64Binary" />
</wsdl:message>
<wsdl:portType name="swatestPortType">
<wsdl:operation name="createNewMessage">
<wsdl:input message="tns:createNewMessageRequest"></wsdl:input>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="swatestBinding" type="tns:swatestPortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="createNewMessage">
<soap:operation
soapAction="http://jdi.communardo.de/swatest/2008-1/createNewMessage" />
<wsdl:input>
<mime:multipartRelated>
<mime:part>
<soap:body parts="newMessageRequest" use="literal" />
</mime:part>
<mime:part>
<mime:content part="attachment" type="*/*" />
</mime:part>
</mime:multipartRelated>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="swatestService">
<wsdl:port name="swatestPort" binding="tns:swatestBinding">
<soap:address location="http://jdi.communardo.de/" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Der Service stellt eine Operation createNewMessage zur Verfügung, die ein Attachment nach dem SwA-Standard sowie ein paar andere Parameter erwartet.
Für die Erzeugung der Axis2-Typklassen und -Stubklassen wird wie oben schon erwähnt Maven 2 eingesetzt. Für Maven 2 gibt es das axis2-wsdl2code-maven-plugin. Mit dem folgenden Maven 2 pom und mvn package können die Klassen aus der obigen WSDL generiert werden:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.communardo.jdi</groupId>
<artifactId>techblog-wsgen</artifactId>
<name>Techblog WSDL Axis2 Testprojekt</name>
<version>1.0-SNAPSHOT</version>
<description>Techblog WSDL Axis2 Testprojekt</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-wsdl2code-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>wsdl2codeSwA</id>
<goals>
<goal>wsdl2code</goal>
</goals>
<configuration>
<packageName>de.communardo.jdi.swatest</packageName>
<wsdlFile>${basedir}/src/main/wsdl/MySwA.wsdl</wsdlFile>
<databindingName>adb</databindingName>
<generateAllClasses>true</generateAllClasses>
<generateServerSide>true</generateServerSide>
<generateServerSideInterface>true</generateServerSideInterface>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2</artifactId>
<version>1.4</version>
<scope>provided</scope>
</dependency>
<!--AXIOM Dependencies-->
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-impl</artifactId>
<version>1.2.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-api</artifactId>
<version>1.2.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-dom</artifactId>
<version>1.2.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.neethi</groupId>
<artifactId>neethi</artifactId>
<version>2.0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.schema</groupId>
<artifactId>XmlSchema</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>stax</groupId>
<artifactId>stax-api</artifactId>
<version>1.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>wstx-asl</artifactId>
<version>3.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>backport-util-concurrent</groupId>
<artifactId>backport-util-concurrent</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
<!-- JMS Dependencies -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>
Da der Buildprozess in dem Projekt noch auf Maven 1 basiert und ich trotzdem nicht auf die Codegenerierung verzichten wollte, habe ich aus dem Maven 1 Buildprozess heraus Maven 2 aufgerufen. Dazu waren folgende Einträge in der Maven 1 project.xml und maven.xml erforderlich:
project.xml:
...
<build>
<defaultGoal>jdi:build</defaultGoal>
<sourceDirectory>${basedir}/target/generated-sources/axis2/wsdl2code</sourceDirectory>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
</build>
...
maven.xml:
...
<goal name="jdi:build"
description="Installiert zip in lokale Repos"
prereqs="jdiInternal:generate,jar:install">
</goal>
<!-- Ruft Maven 2 auf, um den Sourcecode zu generieren. -->
<goal name="jdiInternal:generate">
<ant:echo message="${os.name}" />
<ant:exec executable="mvn.bat" os="Windows 2000, Windows XP" dir="${basedir}">
<arg line="package" />
</ant:exec>
<ant:exec executable="mvn" os="Linux, SunOS, Unix" dir="${basedir}">
<arg line="package" />
</ant:exec>
</goal>
...
Die Unterscheidung nach Betriebssystem ist leider nötig, da ant:exec unter Windows nicht die Dateiendung ergänzt, wie man dies von der Kommandozeile gewohnt ist.
Für Axis2 gibt es einen mitgelieferten JMS-Connector, allerdings stellte sich heraus, dass dieser für unsere Zwecke unbrauchbar war. Der Connector konnte weder mit unidirektionalen WebServices umgehen noch konnte er unsere SwA-Nachricht transportieren. Um trotzdem JMS nutzen zu können, habe ich die Stub-Klassen angepasst, so dass sie die Attachments korrekt im MessageContext integrieren und das eine eigene AXIOM-basierte JMS-Client-Implementierung die JMS-Nachrichten in der überschriebenen invoke(MessageContext)-Methode zusammenbaut. Im Gegensatz zum bei Axis2 mitgelieferten JMS-Connector wird dabei ein JMSByteMessage verwendet und die SwA-Serialisierung von Axis2 genutzt. Als Proof-of-Concept habe ich das Gleiche auch mit einer MTOM-Nachricht hinbekommen, dies wurde allerdings von dem WebService-Endpoint auf der Gegenseite nicht unterstützt.
Wichtig ist, dass der Stub folgendermaßen initialisiert wird:
JMSSwaTestStub stub = new JMSSwaTestStub(config, jmsEndpointUrl.toString());
stub._getServiceClient().getOptions().setUseSeparateListener(false);
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_SWA, Constants.VALUE_TRUE);
stub._getServiceClient().getOptions().setProperty(Constants.Configuration.ENABLE_MTOM, Constants.VALUE_FALSE);
Das Attachment wird folgendermaßen in den MessageContext geschrieben:
try {
String contentId = _messageContext.addAttachment(attachmentData);
SwaRef swaref = new SwaRef();
swaref.setSwaRef(new URI("cid:" + contentId));
newMessage5.getNewMessage().setAttachmentForm(swaref);
} catch (MalformedURIException mue) {
throw new AxisFault(mue.getLocalizedMessage(), mue);
}
Die Verwendung einer ByteMessage wird folgendermaßen erzwungen:
_messageContext.setProperty(JMSConstants.JMS_MESSAGE_TYPE, JMSConstants.JMS_BYTE_MESSAGE);
ActiveMQ bietet mehrere Möglichkeiten zur Überwachung der Queues dazu gehören
Per JMX lassen sich sehr leicht die Namen der Queues und ihr Füllstand überwachen. Bei Fehlern werden Nachrichten in eine spezielle Dead Letter Queue geschrieben. Zusätzlich haben wir für Applikationsfehler noch eine Error-Queue definiert, die der Konsument der JMS-Nachrichten genutzt hat, um auf Fehler von der HTTP/SOAP-Gegenstelle zu reagieren.
Der Einsatz von ActiveMQ als Message-Queuing Middleware hat sich für das Projekt gelohnt, auch wenn uns der schlechte JMS-Support in Axis2 einige Stolpersteine in den Weg gelegt hat.
ActiveMQ bietet auch eine ganze Reihe von Möglichkeiten, die wir im Rahmen des Projektes nicht ausschöpfen konnten. Interessant sind dabei z.B. die Konnektoren zu anderen Transportprotokollen als JMS, z.B. XMPP (Jabber), OpenWire und Stomp. Die Dokumentation auf der ActiveMQ Webseite (http://activemq.apache.org/) ist sehr umfangreich, aber insbesondere im Hinblick auf die Konfiguration nicht immer ganz aktuell und vollständig. Hier mussten wir gelegentlich einen Blick in den zum Glück verfügbaren Sourcecode werfen, um alles richtig zu konfigurieren.
Bei Axis2 sieht das Bild etwas getrübter aus. Für “normale” Einsatzgebiete wie reine HTTP/SOAP-WebServices ist die Unterstützung sehr gut, allerdings stößt man bei Attachments und insbesondere der Nutzung anderer Transportprotokolle, in unserem Fall JMS, schnell an Grenzen und Fehler und muss sich selbst helfen. Auch die Dokumentation auf der Projektwebseite (http://ws.apache.org/axis2/) ist recht lückenhaft. Axis2 hat allerdings eine große und aktive Entwicklercommunity, so dass zu hoffen ist, das sich diese Situation im Laufe der Zeit verbessert.
Releaseparty at Atlassian? Confluence 3.2 BETA and 3.1.2 with soms bugfixes were released yesterday. [...]
Tino Schmidt's Vortrag zu Enterprise Mashups auf der webciety, 4.3 Remix the Web http://bit.ly/d26rtA [...]
neuer Blogpost: February Cumulative Update (2010) http://bit.ly/cwxZGE [...]
Webinar am 16.03.: „Communote Enterprise Microblogging - Funktionen und Einsatzbereiche im Unternehmen“ http://bit.ly/96eexF [...]