blog.smart-java.nl
Ordina J-Technologies – Java Blog



Webservices over MQ

By: Jethro Bakker, 8 May 2008

In een vorig project heb ik kennis gemaakt met het Spring Webservices framework. Met dit framework is het eenvoudig om een POJO als webservice beschikbaar te stellen over HTTP. In versie 1.5 is er ondersteuning toegevoegd voor MQ en SMTP. Hoe werkt dat?

Je definieert allereerst een DefaultMessageListenerContainer. De container heeft o.a. een connection factory en een destination als parameter. Daarnaast geeft je een messageListener op:

<property name="messageListener">
  <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
    <property name="messageFactory" ref="messageFactory"></property>
    <property name="messageReceiver" ref="messageDispatcher"></property>
  </bean>
</property>

De WebServiceMessageListener komt zoals je ziet uit het Spring Webservices framework en heeft twee parameters, een message factory en een dispatcher.

Message Factory
De message factory kan bijvoorbeeld een SoapMessageFactory zijn. In ons geval niet want we gebruiken geen SOAP maar Plain Old XML (POX). Spring Webservices biedt hiervoor een DomPoxMessageFactory. Zoals de naam al aangeeft is deze implementatie gebaseerd op een DOM XML parser. Bij grote documenten kan dit veel geheugen opslokken. Mijn voorkeur gaat daarom uit naar Stax of Sax implementatie. Helaas zijn deze niet out of the box beschikbaar. Daarom heb ik er zelf eentje geschreven. De StaxMessageFactory implementeert de interface WebServiceMessageFactory. Deze interface bevat twee methoden om een WebServiceMessage te creeeren. Een methode creeert een leeg bericht en de andere een bericht op basis van een InputStream.

Message Dispatcher & Endpoint Mappings
De message dispatcher wordt wel out of the box geleverd. De dispatcher bevat een lijst van endpoint mappings. Voorbeelden van endpoint mappins zijn:
SoapActionEndpointMapping – dispatchen op basis van Soap Action
PayloadRootQNameEndpointMapping – dispatchen op basis van root XML element

De klant waar ik voor werkzaam ben heeft een specifieke wens. Er moet gedispatcht worden op basis van een attribuut van het root element. Hiervoor moet er zelf een EndpointMapping geschreven worden. Dat is gelukkig heel eenvoudig. Op basis van de AbstractMapBasedEndpointMapping kun je snel je eigen implementatie schrijven. Deze klasse dwingt af dat je twee methoden implementeert: validateLookupKey en getLookupKeyForMessage. Uiteindelijk configureer je het als volgt in Spring.

<bean id="endpointMapping" class="*MyOwnEndpointMapping">
  <property name="rootElementName" value="MyMessage"></property>
  <property name="rootAttributeName" value="MyAttribute"></property>
  <property name="mappings">
  <props>
    <prop key="A">EndpointA</prop>
    <prop key="B">EndpointB</prop>
    <prop key="C">EndpointC</prop>
  </props>
</property>
</bean>

De MyOwnEndpointMapping is nog te configureren met twee extra parameters: rootElementName en rootAttributeName. Deze parameters worden gebruikt om het attribuut te selecteren en te controleren of de root element naam klopt.De endpoints die we gebruiken overerven van AbstractMarshallingPayloadEndpoint omdat we Castor gebruiken om objecten te marshallen. Een voorbeeld van een endpoint:

public class EndpointC extends AbstractMarshallingPayloadEndpoint {
  public EndpointC(Marshaller marshaller) {
    super(marshaller);
  }
 
  public Object invokeInternal(Object request) {
    MyObject myObject = (MyObject) request;
    return myObject;
  }
}

Het aardige van deze aanpak is dat het niet uitmaakt voor de onderliggende implementatie welk transport mechanisme je gebruikt. Daarnaast is het ook niet van belang wat voor Marshaller er gebruikt wordt. Castor, JAXB het maakt voor de onderliggende implementatie niet uit. Hierdoor zijn de Endpoints makkelijk te unittesten en ook eenvoudig.

Door deze aanpak krijgen we nu ook functionaliteit cadeau. Bijvoorbeeld validatie en log functionaliteit. Door middel van interceptors kan dit eenvoudig ingeregeld worden. Aan de MyOwnEndpointMapping voeg je een property interceptors toe:

  <property name="interceptors">
    <list>
      <ref bean="loggingInterceptor" />
    </list>
  </property>
  ...
  <bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />

Mocht in de toekomst toch SOAP gebruikt gaan worden dan is dit eenvoudig aan te passen door een andere message factory te gebruiken. De endpoint mapping zal dan ook aangepast moeten worden.

Laat een reactie achter