Search This Blog

Thursday 9 January 2014

Handlers and Interceptors in CXF - Are they related ?

I have been trying out some things in CXF (and also feeling pretty good about it :P ) The other day I needed to modify the message of more than one web service in a common manner. I achieved the same using a CXF interceptor. However another team member did the same task using Handlers. Interestingly Handler is more a part of the JAX-WS vocabulary than Interceptor. So I decided to look at this option too.
What exactly is a handler? 
From jax-ws.java.net site
Handlers are message interceptors that can be easily plugged in  to the JAX-WS runtime 
to do additional processing of the inbound and outbound messages. Handlers are invoked 
with a message context that provides methods to access and modify inbound and 
outbound messages and to manage a set of properties.
I decided to add one such Handler to the wsdl first example:
public class CrazyHandler implements LogicalHandler<LogicalMessageContext> {

  private static final Logger LOG = Logger.getLogger(CrazyHandler.class.getName());

  @Override
  public boolean handleMessage(final LogicalMessageContext context) {
    LOG.info("call came handleMessage");
    return true;
  }

  @Override
  public boolean handleFault(final LogicalMessageContext context) {
    LOG.info("call came handleFault");
    return false;
  }

  @Override
  public void close(final MessageContext context) {
    LOG.info("call came close");
  }

}
The class implements a Logical Handler. The javax.xml.ws.handler.LogicalHandler instances "are protocol-agnostic and cannot change any protocol-specific parts (like headers) of a message. Logical handlers act only on the payload of the message."
We need to bring this handler into play. That means configuring CXF to use this handler.
<jaxws:endpoint id="randomWs"
      implementor="com.ws.service.samplews_ns.SampleServiceOperationsPortTypeImpl"
      wsdlLocation="random.wsdl" address="randomService">
        <jaxws:handlers>
            <bean class="com.ws.service.samplews_ns.handler.CrazyHandler"/>
        </jaxws:handlers>
    </jaxws:endpoint>
As seen here we have associated a handler with our endpoint.
If we now start our server:
1431 [localhost-startStop-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  -
Creating instance of bean 'com.ws.service.samplews_ns.handler.CrazyHandler#17e7f88'
Thus for our endpoint an inner bean instance was created of type CrazyHandler and associated with it.
Now If I were to test the code:
final String endpointAddress = "http://localhost:8080/Handler/services/randomService";
final JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(SampleServiceOperationsPortType.class);
//the SEI
factory.setAddress(endpointAddress);
final SampleServiceOperationsPortType client = (SampleServiceOperationsPortType) factory.create();

    final GetRandomRequest randomRequest = new GetRandomRequest();
    randomRequest.setName("coolio");
    final GetRandomResponse reply = client.random(randomRequest);
    System.out.println("Server said: " + reply.getValue());
For this request, I decided to watch the behavior from the logs. Here are some observations:
  1.  When CXF received the request, it created an interceptor chain to handle this request:
    224440 [http-bio-8080-exec-3] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
    - Chain org.apache.cxf.phase.PhaseInterceptorChain@cce15ab was modified. Current flow:
      receive [PolicyInInterceptor, AttachmentInInterceptor]
      pre-stream [CertConstraintsInterceptor]
      post-stream [StaxInInterceptor]
      read [WSDLGetInterceptor, ReadHeadersInterceptor, SoapActionInInterceptor, StartBodyInterceptor]
      pre-protocol [MEXInInterceptor, MustUnderstandInterceptor]
      pre-protocol-frontend [SOAPHandlerInterceptor, LogicalHandlerInInterceptor]
      post-protocol [CheckFaultInterceptor, JAXBAttachmentSchemaValidationHack]
      unmarshal [DocLiteralInInterceptor, SoapHeaderInterceptor]
      pre-logical [OneWayProcessorInterceptor]
      post-logical [WrapperClassInInterceptor]
      pre-invoke [SwAInInterceptor, HolderInInterceptor]
      invoke [ServiceInvokerInterceptor]
      post-invoke [OutgoingChainInterceptor, StaxInEndingInterceptor]
    
    
  2. As seen above, CXF actually used an interceptor to execute the handler. If we look closely, we can see that the PhaseInterceptorChain passes control to the LogicalHandlerInInterceptor in the pre-protocol-frontend phase. This interceptor than invokes all handlers for the inbound flow i.e. incoming request. accordingly our CrazyHandler is invoked and the message is logged.
    ...
    224460 [http-bio-8080-exec-3] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
    - Invoking handleMessage on interceptor org.apache.cxf.jaxws.handler.logical.LogicalHandlerInInterceptor@43e73c9a
    224464 [http-bio-8080-exec-3] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
    - invoking handlers, direction: inbound
    224470 [http-bio-8080-exec-3] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
    - invoking handler of type com.ws.service.samplews_ns.handler.CrazyHandler
    224470 [http-bio-8080-exec-3] INFO  com.ws.service.samplews_ns.handler.CrazyHandler  
    - call came handleMessage
    
  3. In the invoke phase the ServiceInvokerInterceptor begins the call that eventually lands up at our endpoint:
    224496 [http-bio-8080-exec-3] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
    - Invoking handleMessage on interceptor org.apache.cxf.interceptor.ServiceInvokerInterceptor@3d38d0ab
    Nov 27, 2013 11:16:35 AM com.ws.service.samplews_ns.SampleServiceOperationsPortTypeImpl random
    INFO: Executing operation random
    com.ws.service.xsd.random_schema.GetRandomRequest@bcf1913
    
  4. After all the phases associated with the inbound request are processed, CXF creates a separate chain for the response processing:
    224540 [http-bio-8080-exec-3] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
    - Chain org.apache.cxf.phase.PhaseInterceptorChain@586cc05e was modified. Current flow:
      setup [PolicyOutInterceptor]
      pre-logical [HolderOutInterceptor, SwAOutInterceptor, WrapperClassOutInterceptor, SoapHeaderOutFilterInterceptor]
      post-logical [SoapPreProtocolOutInterceptor]
      prepare-send [MessageSenderInterceptor]
      pre-stream [AttachmentOutInterceptor, StaxOutInterceptor]
      pre-protocol-frontend [SOAPHandlerInterceptor]
      write [SoapOutInterceptor]
      pre-marshal [LogicalHandlerOutInterceptor]
      marshal [BareOutInterceptor]
      post-marshal [LogicalHandlerOutEndingInterceptor]
      write-ending [SoapOutEndingInterceptor]
      pre-stream-ending [StaxOutEndingInterceptor]
      prepare-send-ending [MessageSenderEndingInterceptor]
    
  5. Here to if we observe there is the pre-marshal phase which includes a LogicalHandlerOutInterceptor. The class is responsible for invoking our handlers for the outbound flow or the response.
    224549 [http-bio-8080-exec-3] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
    - Invoking handleMessage on interceptor org.apache.cxf.jaxws.handler.logical
    .LogicalHandlerOutInterceptor$LogicalHandlerOutEndingInterceptor@572b0d74
    224554 [http-bio-8080-exec-3] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
    - invoking handlers, direction: outbound
    224555 [http-bio-8080-exec-3] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
    - invoking handler of type com.ws.service.samplews_ns.handler.CrazyHandler
    224555 [http-bio-8080-exec-3] INFO  com.ws.service.samplews_ns.handler.CrazyHandler  
    - call came handleMessage
    
    Thus the call lands up in our handler's handleMessage for a second time.
  6. Once the chain completes processing the servlet method also ends.
    224560 [http-bio-8080-exec-3] DEBUG org.apache.cxf.transport.servlet.ServletController  
    - Finished servicing http request on thread: Thread[http-bio-8080-exec-3,5,main]
    
From what we have seen yet we can conclude that
  • Handlers is more generic term defined as a part of the JAX-WS specification. It concerns with filter like behavior for web services.
  • CXF has implemented the above concept of filter like processing with interceptors. It also supports the more generic mechanism of handlers.
  • The implementation of JAX-WS handlers in CXF is achieved using interceptors.
  • This is also clear from the packages. The Handlers and related classes fall within the javax package. The interceptors and related classes land up in the cxf packages and jars.
There is a lot more in this topic. I found this excellent link that further list some more subtle differences between the two methods of processing available with CXF.

3 comments:

  1. Thanks for your article :-).

    I have a very concrete doubt: In my client I receive a WSDL with a policy for usernameToken. That policy also has Nonce and Created.

    Apache CXF detects policy on WSDL and launches PolicyInterceptor. Even if you are using WSS4JOutInterceptor, WSSecurityPolicyLoader will load UsernameTokenInterceptorProvider, That turns into triggering UsernameTokenInterceptor to build security header.

    Problem is that UsernameTokenInterceptor don´t check WSDL policy nonce or created and therefore, doesn´t use UsernameToken "addNonce" and "addCreated" methods.

    Do you have any idea how can I add Nonce and Created to security header? As I said, WSS4JOutInterceptor is not valid because UsernameTokenInterceptor is launched (and then you will have two security headers).

    Regards and thanks again for you article.

    ReplyDelete
  2. Nice article bro, thx

    ReplyDelete