Friday, January 1, 2010

Spring Webservices

On my current project I'm using Spring Webservices to interact with external companies. This contract-first framework is easy to use and hides most of the boiler plate code, so you can fully focus on the business part.

Here's a basic example that uses XmlBeans to marshall/unmarshall the XML to and from java objects. (You can also use Jibx, Xstream, Castor, JAXB, ... ).
All you need to do to use the XmlBeans marshaller, is pass it to the super constructor. The AbstractMarshallingPayloadEndpoint is usefull when you only need to process the payload XML. Only the XML is passed in as an argument to the invokeInternal() method.

public class MyEndpoint extends AbstractMarshallingPayloadEndpoint{

private final MyService service;

public MyEndpoint(MyService service, Marshaller xmlBeanMarshaller) {
super(xmlBeanMarshaller);
this.service = service;
}

protected Object invokeInternal(Object request) throws Exception {

XmlBeanRequestDocument requestXml = (XmlBeanRequestDocument) request;
Object result = service.findById(requestXml.getRequest().getId());

//marshall the result
XmlBeanResponseDocument responseXml = XmlBeanResponseDocument.Factory.parse(result);
return responseXml;
}
}

You can declare your endpoint in a number of endpoint mappers. One of the easiest to use, is the payload endpoint mapper. The mapper is an interceptor that looks at the root element of the payload to route the message to the proper endpoint.

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
<prop key="{http://mycompany.com/hr/schemas}MyRequest">myEndpoint</prop>
</props>
</property>
</bean>

<bean id="myEndpoint" ... />

The advantage of this kind of mapping, is that you can host all your webservices on the same url.

Let's say you want access to more than just the payload, meaning you also need to deal with attachments. Here is a simple example giving you an idea on how easy it is to implement your custom endpoint that does this :

public class DetachEndpoint implements MessageEndpoint {

public void invoke(MessageContext messageContext) throws Exception {

WebServiceMessage requestMessage = messageContext.getRequest();

if (requestMessage instanceof AbstractSoapMessage) {

@SuppressWarnings("unchecked")
Iterator attachments = ((AbstractSoapMessage) requestMessage).getAttachments();

while (attachments.hasNext()) {
Attachment attachment = attachments.next();

... //insert business logic here

//System.out.println("attachment = " + attachment);
//System.out.println("attachment datahandler = " + attachment.getDataHandler());
//System.out.println("attachment content-id: " + attachment.getContentId());
//System.out.println("attachment content type: " + attachment.getContentType());
//System.out.println("attachment size: " + attachment.getSize());
}
} else {
//throw some sort of exception
}
}
}

The messageContext gives you access to the attachments and to the payload XML via requestMessage.getPayloadSource(). This is very basic to show you the principle. A better way to do this, is to have an AbstractMarshallingPayloadEndpoint with a custom interceptor for incoming messages, that does logic similar to my DetachEndpoint.

The class implementing the EndpointInterceptor interface, can be declared in your config like this :

<bean id="myEndPointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
...
</props>
</property>
<property name="interceptors">
<list>
<ref bean="myInterceptor" />
....
</list>
</property>

<bean id="myInterceptor" ... />

The interceptor has access to the messageContext via the handleRequest(MessageContext messageContext) method.