Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
0800 1 255 255

Apache ActiveMQ und Axis2 mit JMS

In einem aktu­el­len Kundenprojekt hat­ten wir die Anforderung eine Message-Queue zu imple­men­tie­ren, die neben dem rei­nen Queuing eine garan­tierte Zustellung zum Zielsystem gewähr­leis­ten sollte. Es musste sicher­ge­stellt wer­den, dass auch bei Nichtverfügbarkeit des Zielsystems keine Nachrichten ver­lo­ren gehen.

Da keine zusätz­li­chen Kosten für Softwarelizenzen anfal­len soll­ten und ich das Rad nicht neu erfin­den wollte, habe ich nach einer Lösung im OpenSource-Bereich gesucht. Mit Apache ActiveMQ bin ich fün­dig gewor­den. Innerhalb der ver­teil­ten Applikation wurde Axis2 ver­wen­det. Das Zielsystem ist per SOAP/HTTP zu errei­chen, wäh­rend ActiveMQ eine Message-Queue auf JMS-Basis dar­stellt.

Um das Queuing zu imple­men­tie­ren, war fol­gende Reihenfolge der Nachrichtenübermittlung erfor­der­lich:

  • Webapplikation erzeugt SOAP-Nachrichten (mit Attachments) und ver­packt diese in JMS-Nachrichten
  • JMS-Nachrichten wer­den von der Webapplikation in eine ActiveMQ-Queue geschrie­ben
  • Eine selbst­ent­wi­ckelte Komponente holt die SOAP-Nachrichten aus der Queue
  • die selbe Komponente baut HTTP-Verbindungen zum Zielsystem auf und schickt die SOAP-Nachrichten über die HTTP-Verbindung

Initial sah die Implementierung ziem­lich tri­vial aus, aller­dings erga­ben sich im Projektverlauf einige Schwierigkeiten.

Axis2 Stubs mit Maven 1 erzeugen

Da es für Axis2 kein Maven 1 Plugin gibt, bin ich für die Generierung der Stubklassen auf Maven 2 aus­ge­wi­chen und habe dies in unse­ren Maven 1 Buildprozess inte­griert.

WSDL-Beschreibung

Der Service wird durch fol­gende WSDL beschrie­ben:

<?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 erwar­tet.

Erzeugen der Typklassen und Stubs mit Maven 2

Für die Erzeugung der Axis2-Typklassen und ‑Stubklassen wird wie oben schon erwähnt Maven 2 ein­ge­setzt. Für Maven 2 gibt es das axis2-wsdl2code-maven-plugin. Mit dem fol­gen­den Maven 2 pom und mvn package kön­nen die Klassen aus der obi­gen WSDL gene­riert wer­den:

<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>

Aufruf von Maven 2 aus dem Maven 1 Buildprozess

Da der Buildprozess in dem Projekt noch auf Maven 1 basiert und ich trotz­dem nicht auf die Codegenerierung ver­zich­ten wollte, habe ich aus dem Maven 1 Buildprozess her­aus Maven 2 auf­ge­ru­fen. Dazu waren fol­gende Einträge in der Maven 1 project.xml und maven.xml erfor­der­lich:

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 lei­der nötig, da ant:exec unter Windows nicht die Dateiendung ergänzt, wie man dies von der Kommandozeile gewohnt ist.

Axis2 1.4 und JMS

Für Axis2 gibt es einen mit­ge­lie­fer­ten JMS-Connector, aller­dings stellte sich her­aus, dass die­ser für unsere Zwecke unbrauch­bar war. Der Connector konnte weder mit uni­di­rek­tio­na­len WebServices umge­hen noch konnte er unsere SwA-Nachricht trans­por­tie­ren. Um trotz­dem JMS nut­zen zu kön­nen, habe ich die Stub-Klassen ange­passt, so dass sie die Attachments kor­rekt im MessageContext inte­grie­ren und das eine eigene AXIOM-basierte JMS-Client-Implementierung die JMS-Nachrichten in der über­schrie­be­nen invoke(MessageContext)-Methode zusam­men­baut. Im Gegensatz zum bei Axis2 mit­ge­lie­fer­ten JMS-Connector wird dabei ein JMSByteMessage ver­wen­det und die SwA-Serialisierung von Axis2 genutzt. Als Proof-of-Concept habe ich das Gleiche auch mit einer MTOM-Nachricht hin­be­kom­men, dies wurde aller­dings von dem WebService-Endpoint auf der Gegenseite nicht unter­stützt.

Wichtig ist, dass der Stub fol­gen­der­ma­ßen initia­li­siert 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 fol­gen­der­ma­ßen in den MessageContext geschrie­ben:

    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 fol­gen­der­ma­ßen erzwun­gen:

    _messageContext.setProperty(JMSConstants.JMS_MESSAGE_TYPE, JMSConstants.JMS_BYTE_MESSAGE);

ActiveMQ-Überwachung

ActiveMQ bie­tet meh­rere Möglichkeiten zur Überwachung der Queues dazu gehö­ren

  • WebInterface
  • RSS- und Atom-Feeds
  • JMX

Per JMX las­sen sich sehr leicht die Namen der Queues und ihr Füllstand über­wa­chen. Bei Fehlern wer­den Nachrichten in eine spe­zi­elle Dead Letter Queue geschrie­ben. Zusätzlich haben wir für Applikationsfehler noch eine Error-Queue defi­niert, die der Konsument der JMS-Nachrichten genutzt hat, um auf Fehler von der HTTP/SOAP-Gegenstelle zu reagie­ren.

Fazit

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 bie­tet auch eine ganze Reihe von Möglichkeiten, die wir im Rahmen des Projektes nicht aus­schöp­fen konn­ten. Interessant sind dabei z.B. die Konnektoren zu ande­ren Transportprotokollen als JMS, z.B. XMPP (Jabber), OpenWire und Stomp. Die Dokumentation auf der ActiveMQ Webseite (http://activemq.apache.org/) ist sehr umfang­reich, aber ins­be­son­dere im Hinblick auf die Konfiguration nicht immer ganz aktu­ell und voll­stän­dig. Hier muss­ten wir gele­gent­lich einen Blick in den zum Glück ver­füg­ba­ren Sourcecode wer­fen, um alles rich­tig zu kon­fi­gu­rie­ren.

Bei Axis2 sieht das Bild etwas getrüb­ter aus. Für "nor­male" Einsatzgebiete wie reine HTTP/SOAP-WebServices ist die Unterstützung sehr gut, aller­dings stößt man bei Attachments und ins­be­son­dere der Nutzung ande­rer Transportprotokolle, in unse­rem Fall JMS, schnell an Grenzen und Fehler und muss sich selbst hel­fen. Auch die Dokumentation auf der Projektwebseite (http://ws.apache.org/axis2/) ist recht lücken­haft. Axis2 hat aller­dings eine große und aktive Entwicklercommunity, so dass zu hof­fen ist, das sich diese Situation im Laufe der Zeit ver­bes­sert.

Related Posts

Pin It on Pinterest