Search This Blog

Wednesday 12 March 2014

The Different Type of JAX-WS Handlers available

In the last post we saw how we could use Handlers to access various properties associated with the web service requests and responses. But can we modify them ? I guess it is time the CrazyHandler actually did something crazy !
We have the clients sending us a name property in the Request objects. My CrazyHandler class has gone all crazy and plans to ensure that some invalid value is received by the web service endpoint for all clients irrespective of what is actually sent by the client.
public boolean handleMessage(final LogicalMessageContext context) {
    final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
    if (Boolean.FALSE.equals(outboundProperty)) {
      final LogicalMessage logicalMessage = context.getMessage();
      final GetRandomRequest getRandomRequest = (GetRandomRequest) logicalMessage.getPayload(jContext);
      getRandomRequest.setName("Invalid!!!");
      logicalMessage.setPayload(getRandomRequest, jContext);
    }
    return true;
  }
As seen above for the inbound flow, I have accessed the soap message or content present in the soap body. I can retrieve the value as an XML source. Even better if I am aware of the Object XML mapper used, I can marshal this XML into actual java objects (much easier to work with).
Accordingly I created my JAXBContext:
final static private JAXBContext jContext;

  static {
    try {
      // must supply information of all packages having Complex Elements here
      jContext = JAXBContext.newInstance("com.ws.service.xsd.random_schema");
    } catch (final JAXBException e) {
      throw new RuntimeException(e);
    }
  }
It is important to note that the Source or the marshaled object received from the context is a copy of the actual message. To ensure our changes are written to the soap request we need to call the corresponding setter method. That's it. We are done. Our crazy handler is now ready to make all clients crazy.
If this scenario was real, then over time my clients would realize that they are being tricked. To overcome this corruption of message body, they come up with a weird solution (Ya a lot of weird things in this post). They have decided to place the name property in the soap header too. So my endpoint will now ignore the body and read the name from the SOAP header.
"Big Deal !!" says CrazyHandler, "If I can intercept the body than why I can't I intercept the header." Accordingly I (I mean, the crazy handler!!) starts hunting for the appropriate soap property in the LogicalMessageContext. And boom ! - no such property.
The LogicalMessageContext has only one getter - for the payload property. No way to access the Header ?? Is this the end of being crazy ??
Actually no. Back to some theory :
JAX-WS defines two types of handlers, logical handlers and protocol 
handlers. Protocol handlers are specific to a protocol and may 
access or change the protocol specific aspects of a message. 
Logical handlers 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.
At the base of the hierarchy we have the Handler interface. All the methods that we have seen in CrazyHandler are defined here.

This is extended by the LogicalHandler interface and the SOAPHandler interface. The SOAPHandler is an example of a Protocol handler. It provides access to the Soap Headers, the SOAP message etc. The LogicalHandler on the contrary provides access to the contained message only. It does not provide indication about the underlying protocol that is in use. Simply the received message. We have seen the LogicalHandler in access. If we need to fiddle with the headers we will require a SOAPHandler:
public class CrazySoapHandler implements SOAPHandler<SOAPMessageContext> {

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

  public boolean handleMessage(final SOAPMessageContext context) {

    final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

    if (Boolean.FALSE.equals(outboundProperty)) {
      final SOAPMessage soapMessage = context.getMessage();
      try {
        soapMessage.getSOAPBody(); //The SOAP Body
        soapMessage.getSOAPHeader(); //The SOAP Header
        SOAPFactory soapFactory = SOAPFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
        //use the SOAPFactory to operate on the various SOAP elements
        soapMessage.getMimeHeaders(); //MIME Headers from the Transport Layer
      } catch (final SOAPException e) {
        throw new RuntimeException(e);
      }
    }
    return true;
  }

  public boolean handleFault(final SOAPMessageContext context) {
    LOG.info("call came - handleFault");
    return false;
  }

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

  public void close(final MessageContext context) {
  }


  public Set<QName> getHeaders() {
    return null;
  }

}
The SOAPMessageContext gives us access to the various SOAP elements such as SOAPMessage, SOAPHeader,SOAPBody etc.The classes are all from the javax.xml.soap package or the SAAJ api. As I do not have much knowledge of their manipulation i shall leave it aside for now.
We need to configure our SoapHandler in the xml file:
<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"/>
            <bean class="com.ws.service.samplews_ns.handler.CrazySoapHandler"/>
        </jaxws:handlers>
    </jaxws:endpoint>
The two handlers have been added to the endpoint. There is an important point of the sequence of execution here. From the web:
"During runtime, the handler chain is re-ordered such that logical handlers are executed before 
the SOAP handlers on an outbound message and SOAP handlers are executed before logical handlers 
on an inbound message."
Thus irrespective of our ordering, the SOAPHandler will be the first to process an incoming message and the last to deal with any outgoing message. In CXF's handler mechanism:
56496 [http-bio-8080-exec-5] DEBUG org.apache.cxf.phase.PhaseInterceptorChain -
 Chain org.apache.cxf.phase.PhaseInterceptorChain@4455b354 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]
 ...
56498 [http-bio-8080-exec-5] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  -
 invoking handler of type com.ws.service.samplews_ns.handler.CrazySoapHandler
 ...
56498 [http-bio-8080-exec-5] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  -
 invoking handler of type com.ws.service.samplews_ns.handler.CrazyHandler
As the log indicates for the inbound message the SOAPHandlerInterceptor executed in the pre-protocol-frontend phase. It is called before the LogicalHandlerInInterceptor.
Similarly for the outbound message:
56720 [http-bio-8080-exec-5] DEBUG org.apache.cxf.phase.PhaseInterceptorChain  
- Chain org.apache.cxf.phase.PhaseInterceptorChain@5ed72cc6 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]
  user-protocol [org.apache.cxf.jaxws.handler.soap.SOAPHandlerInterceptor.ENDING]
  write-ending [SoapOutEndingInterceptor]
  pre-protocol-ending [SAAJOutEndingInterceptor]
  pre-stream-ending [StaxOutEndingInterceptor]
  prepare-send-ending [MessageSenderEndingInterceptor]
...
56723 [http-bio-8080-exec-5] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
- invoking handler of type com.ws.service.samplews_ns.handler.CrazyHandler
...
56723 [http-bio-8080-exec-5] DEBUG org.apache.cxf.jaxws.handler.HandlerChainInvoker  
- invoking handler of type com.ws.service.samplews_ns.handler.CrazySoapHandler

2 comments: