JAX-WS Handlers are perfectly adequate for implementing simple, cross-service features of a Java web-service. For more complex scenarios though, there’s a good chance your web-service framework might have more to offer you. Here we look at using a CXF Interceptor rather than a JAX-WS Handler for header processing.
In CXF – Using Handlers to process SOAP headers in a JAX-WS web service we looked at the use of JAX-WS standard SOAPHandlers as a way of implementing generic header processing across all operations of a web-service.
That they are part of the specification makes SOAPHandler classes more portable across different implementations of the JAX-WS spec. Implementation portability isn’t everybody’s first concern though, and since any open specification is, by definition, the least common denominator of all implementations there is every chance your particular framework will have a little more to offer you when it comes to more complex requirements.
Here we’re going to convert our previous CXFMessenger example to use an Apache CXF interceptor instead of a JAX-WS SOAPHandler and, in particular, look at how to unmarshall headers in a CXF interceptor irrespective of the XML binding technology being used.
Our simple example
We’ll kick off with the same service interface we used in the previous post:-
@WebService(targetNamespace = "http://services.devsumo.com/cxfMessenger/v005") @XmlSeeAlso(ServiceOptions.class) public interface CXFMessenger { @WebMethod String sendMessage( @WebParam(name="recipient") String recipient, @WebParam(name="messageContent") String messageContent); }
And we’re going to use the same ServiceOptions object as an intercepted header. We’ve included an @XmlSeeAlso on our interface to ensure it appears in the WSDL and we’ve marked our class with an @XmlRootElement to make sure our XML marshaller will be able to process it.
@XmlRootElement public class ServiceOptions { private ClassOfService classOfService; public ServiceOptions() { } public void setClassOfService(ClassOfService classOfService) { this.classOfService = classOfService; } public ClassOfService getClassOfService() { return classOfService; } }
Building our Header Interceptor
CXF comes with plenty of base classes to build Interceptors on and plenty of examples which can be extended too. In fact, it can be a little overwhelming working out where to start! Here we’re going to use the AbstractSoapInterceptor as our base class. It’s a common base class of many other Interceptors, and pulls in the necessary interfaces and provides the basic boilerplate code for us:-
import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.phase.Phase; public class ServiceOptionsInterceptor extends AbstractSoapInterceptor { public ServiceOptionsInterceptor() { super(Phase.PRE_INVOKE); } @Override public void handleMessage(SoapMessage message) throws Fault { // TODO: Implement our header handling } }
Unsurprisingly then, this is somewhat briefer than our basic JAX-WS SOAPHandler class was. We’ve got a similar handleMessage method which we’ll be fleshing out shortly, but first a quick note about Phases. CXF divides its interceptor chains into various processing phases which allows us quite detailed control over when our interceptor will run. For example, we could have it execute as soon as a request is received, before any parsing or marshalling takes place, which is an ideal spot for end-to-end performance monitoring. Alternatively we could have it run immediately before the underlying service method is invoked, which means our interceptor will only run if nothing else beforehand has failed and it can take advantage of all the processing done already by the other parts of the chain. This latter case is the one we’re going for here, by specifying Phase.PRE_INVOKE.
Extracting and unmarshalling the header
The message passed into handleMessage has a convenient getHeader method which takes a QName for the header’s XML type and returns a matching Header if there is one:-
public class ServiceOptionsInterceptor extends AbstractSoapInterceptor { private static final QName HEADER_TYPE = new QName("http://services.devsumo.com/cxfMessenger/v005", "serviceOptions"); public ServiceOptionsInterceptor() { super(Phase.PRE_INVOKE); } @Override public void handleMessage(SoapMessage message) throws Fault { Header header = message.getHeader(HEADER_TYPE); if(header != null) { // TODO: process our header } } }
On the plus side, unlike our JAX-WS Handler, we don’t have to give getHeader any sort of marshaller. On the downside though, that’s because it isn’t going to unmarshall anything for us. We’re just getting the parsed XML, we need to take care of binding it to our service objects.
CXF supports a range of XML binding technologies and knowing this suggests that there must be some sort of abstraction layer we can tap into to handle this sort of thing for us.
Sure enough, what we’re looking for here is a DataBinding implementation associated with our Service and we can get that from the Exchange associated with our message:-
@Override public void handleMessage(SoapMessage message) throws Fault { Header header = message.getHeader(HEADER_TYPE); if(header != null) { Service service = ServiceModelUtil.getService(message.getExchange()); DataReader<Node> dataReader = service.getDataBinding().createReader(Node.class); //… } }
We’ve used the returned DataBinding implementation to create a DataReader which accepts an XML node. We can now use this to unmarshall our header:-
@Override public void handleMessage(SoapMessage message) throws Fault { Header header = message.getHeader(HEADER_TYPE); if(header != null) { Service service = ServiceModelUtil.getService(message.getExchange()); DataReader<Node> dataReader = service.getDataBinding().createReader(Node.class); ServiceOptions serviceOptions = (ServiceOptions)dataReader.read(HEADER_TYPE, (Node)header.getObject(), ServiceOptions.class); message.setContextualProperty( ServiceOptions.class.getName(), serviceOptions); } }
Now we have our unmarshalled object we can go on to process our header. For this simple example we just stash it in the message context in the same place our JAX-WS Handler was putting it; hence the same service implementation logic should be able to pick it up:-
public class CXFMessengerImpl implements CXFMessenger { @Resource protected WebServiceContext wsContext; private ServiceOptions retrieveServiceOptions() { WrappedMessageContext wrappedMessageContext = (WrappedMessageContext)wsContext.getMessageContext(); return (ServiceOptions)(wrappedMessageContext. getWrappedMessage().getContextualProperty( ServiceOptions.class.getName())); } @Override public String sendMessage(String recipient, String messageContent) { ServiceOptions serviceOptions = retrieveServiceOptions(); System.out.println("Sending " + (serviceOptions != null ? serviceOptions.getClassOfService() : "") + " message \"" + messageContent + "\" to \"" + recipient); // … } }
Running our Interceptor
Finally we need to get our Interceptor registered with CXF. Again, I’m using Spring here and the model is broadly similar to registering a JAX-WS handler – we create a bean for it and add it to the chain to be executed. Unlike the JAX-WS Handlers though, we register our CXF Interceptors onto the CXF bus and we have a bit more control over when and how they’ll run too. Here we’re just registering ours to run on inbound requests:-
<cxf:bus> <cxf:inInterceptors> <bean id="serviceOptionsInterceptor" class="com.devsumo.cxfmessenger.v005.ServiceOptionsInterceptor"/> </cxf:inInterceptors> </cxf:bus>
Obviously it’s a little hard to demonstrate the extra power of CXF Interceptors over JAX-WS handlers with a simple example like this where there’s little to choose between the two. Yet even here you can start to see some of the potential interceptors may offer you; the more fine-grained control available in terms of where and when they run, the wide range of example classes and base classes we can extend to leverage other parts of the CXF framework or just re-use for our more complex needs, and the ability to plug in functionality which will be agnostic of the XML binding technology in use. If you’re willing to sacrifice a little portability and standards-adherence, they’re certainly worth a look.
Thanks! It was very helpful to me. How could I test ServiceOptionsInterceptor class using Junit?
The code is reasonably mock-friendly so you could certainly use something like Mockito for a clean, class-under-test specific unit test.
By way of a quick and dirty example, you’d need around 7 mocks:-
Node node = Mockito.mock(Node.class);
Header header = Mockito.mock(Header.class);
SoapMessage message = Mockito.mock(SoapMessage.class);
DataBinding dataBinding = Mockito.mock(DataBinding.class);
Service service = Mockito.mock(Service.class);
Exchange exchange = Mockito.mock(Exchange.class);
DataReader dataReader = Mockito.mock(DataReader.class);
Plus a known ServiceOptions value:-
ServiceOptions serviceOptions = new ServiceOptions();
And a few whens to wire these together:-
Mockito.when(message.getHeader(ServiceOptionsInterceptor.HEADER_TYPE)).thenReturn(header);
Mockito.when(exchange.getService()).thenReturn(service);
Mockito.when(message.getExchange()).thenReturn(exchange);
Mockito.when(service.getDataBinding()).thenReturn(dataBinding);
Mockito.when(dataBinding.createReader(Node.class)).thenReturn(dataReader);
Mockito.when(header.getObject()).thenReturn(node);
Mockito.when(dataReader.read(ServiceOptionsInterceptor.HEADER_TYPE, node, ServiceOptions.class)).thenReturn(serviceOptions);
Then call handleMessage and verify that the correct ServiceOptions object is passed to message.setContextualProperty():-
new ServiceOptionsInterceptor().handleMessage(message);
Mockito.verify(message).setContextualProperty(ServiceOptions.class.getName(), serviceOptions);
An alternative, and to be honest my preference for testing this sort of class, is to use an integration testing style. I’d create a simple-as-possible SOAP service with just the ServiceOptionsInterceptor on the CXF bus, launch it from the JUnit test (if using Spring this is just a matter of initialising the context) and then send in some HTTP calls with well-formed SOAP requests – using the simple service implementation to capture the value of the ServiceOptions property so I can query it after invocation and check it is as expected. This sort of test proves that the code is correctly interacting with the framework classes, actually does what it’s supposed to do, and is much more useful when making future code changes.
As someone new to Java and CXF, I just wanted to say thanks for your well explained guides.