Glen Mazza's Weblog

« Sending Custom Metri... | Main | Replacing JAX-WS... »

https://glenmazza.net/blog/date/20180225 Sunday February 25, 2018

Adding JAX-WS handlers to SOAP web services and clients

Also see this blog entry for information on CXF interceptors and alternative simpler methods of modifying SOAP requests and responses.

This tutorial shows various usages of JAX-WS handlers and how they can be attached to CXF-based SOAP web services and clients. We'll be attaching the handlers to the DoubleIt web service and client. The finished tutorial source code can be cloned or downloaded from GitHub.

The design and behavior of JAX-WS handlers are primarily defined in Chapter 9 of the JAX-WS Specification, with additional relevant material in Sections 4.2.1, 5.3, and 10. Some main features of handlers:

  • Handlers fall into two categories:
    • SOAP Handlers (or, more precisely, protocol handlers, as JAX-WS supports other non-SOAP protocols such as XML over HTTP), which provide the ability to modify the entire SOAP envelope using the DOM-like SAAJ.
    • LogicalHandlers with provide access to the SOAP body's content (the payload) only. For logical handlers, JAXB or one of the Source options is used to modify the message instead of the protocol-specific SAAJ.
  • Handlers are linked in predefined chains except will be reordered such that logical handlers always come before SOAP handlers.
  • Handler chains are defined and attached separately to the client and/or service provider. Each handler chain is activated in both directions, inbound and outbound, however developers can use the OUTBOUND Boolean property of the MessageContext to determine whether or not to activate logic within those handlers.
  • As specified in Section 9.3.2 of the JAX-WS spec, for outbound messages "processing starts with the first handler in the chain and proceeds in the same order as the handler chain. For inbound messages the order of processing is reversed: processing starts with the last handler in the chain and proceeds in the reverse order of the handler chain." So assuming a handler chain of "H1->H2->H3", attaching such a chain to a web service provider would result in "H3->H2->H1->web service->H1->H2-H3" ordering.
  • Handlers carry a MessageContext, a threadsafe mechanism holding request/response data between the handlers of a chain, and between the client or service endpoints and the handlers.
  • Handler functionality does not cross the client - service boundary. With SOAP the only data passed between the client and service remains the SOAP message and (if using HTTP) any associated HTTP headers.
  • Handlers are commonly attached to the SOAP client via a Java API, and to the web service via an XML configuration file. CXF offers additional options.

The Metro-based JAX-WS handler samples (in its WSDL-first and Java-first versions) provide a good example of using Handlers to log incoming and outgoing SOAP messages, from both the client and web service provider. This code can be used as-is with CXF. We'll expand on these examples by modifying the SOAP message, processing the SOAP request without the web service being activated, and using the MessageContext to pass property values between (client or service) endpoint and handlers. The following list of handler capabilities shows some of the functionality covered:

JAX-WS 2.1 Spec Reference Functionality How Demonstrated Below
9.1.1

Logical Handlers may manipulate the message payload (SOAP body in the case of SOAP binding.)

The client logical handler will change the value of the SOAP request to a MAX_VALUE MessageContext property set by the SOAP client, if its value exceeds that property.

9.1.1

Protocol Handlers may access and change protocol specific aspects (e.g., the SOAP header) of a message.

The client SOAPHandler will add two elements to the SOAP header.

9.3.2.1

For the request-response MEP [Message Exchange Pattern], returning false from a Handler's handleMessage reverses the message direction. E.g., during outbound processing a client-side handler can process a SOAP request and return a SOAP response without need to call the web service.

Not implemented here; code demonstrating this already available in this CXF sample.

9.3.2.1

For the request-response MEP, throwing a SOAPFaultException from a Handler's handleMessage reverses the message flow and invokes handleFault on the next handler. E.g., a client-side handler can trap errors, halt the SOAP request and return a SOAPFault back to the SOAP client.

The client- and service-side logical handlers will return a SOAPFault error back to the client if there is an attempt to double the number 20 or 30, respectively. In both cases, the Service Endpoint Interface (SEI) implementation will not be activated.

9.4.1

MessageContext properties are scoped either HANDLER (default) or APPLICATION. The former are available just to the handlers of a chain, and the latter are available both to the handlers and the endpoint that the handler chain is attached to.

During service-side inbound processing, the service SOAPHandler will read the two elements added by the client SOAPHandler and add those values to the MessageContext, one with HANDLER and the other with APPLICATION scope. The SEI implementation will be able to read only the latter.

9.4.1.1

Tables 9.2 - 9.4 of the JAX-WS Specification lists several standard message context properties. Some are applicable only if HTTP is being used and/or the web service is being deployed in a servlet container. These properties appear to all have APPLICATION scope.

HandlerUtils.java has methods that will output MessageContext property values. It can be called from within the endpoints and handlers as desired.

Steps:

  1. Download and deploy the DoubleIt web service on Tomcat. This is just a check to make sure the DoubleIt web service and client is working properly on your machine before adding the handlers. Best not to proceed unless the SOAP calls from that tutorial can be made successfully.

  2. Create the HandlerUtils.java class to output the MessageContext properties. Although this class would ideally be kept in just one place, to avoid architectural complexity for this tutorial we can add this class to both the client and service packages. Note this class is not exhaustive in the MessageContext properties it reports on--it does not output the HTTP header values and Servlet properties, for example.

  3. Create the client-side handlers. For tutorial convenience, I'm nesting both handlers within a single class. Place the following ClientHandlers.java class in the Client submodule's client package:

  4. Update the SOAP client to use the handlers. Replace the WSClient.java file from Step #9 of the DoubleIt tutorial with the below class.

  5. Create the service-side handlers. I could not nest the service-side handlers into a single class as I could above for the client handlers, CXF, when reading the service-side handler chain configuration file, complains about not being able to instantiate the handlers unless they are declared standalone. At any rate, add the following classes to the service package:

    LogicalHandler.java:
    package service;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.JAXBException;
    import javax.xml.ws.LogicalMessage;
    import javax.xml.ws.ProtocolException;
    import javax.xml.ws.handler.LogicalMessageContext;
    import javax.xml.ws.handler.MessageContext;
    
    import org.example.schema.doubleit.DoubleIt;
    import org.example.schema.doubleit.ObjectFactory;
    
    public class LogicalHandler implements javax.xml.ws.handler.LogicalHandler<LogicalMessageContext> {
    
       @Override
       public void close(MessageContext mc) {
       }
    
       @Override
       public boolean handleFault(LogicalMessageContext messagecontext) {
          return true;
       }
    
       @Override
       public boolean handleMessage(LogicalMessageContext mc) {
          HandlerUtils.printMessageContext("Service LogicalHandler", mc);
          if (Boolean.FALSE.equals(mc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY))) {
             try {
                LogicalMessage msg = mc.getMessage();
                JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
                Object payload = msg.getPayload(jaxbContext);
                if (payload instanceof DoubleIt) {
                   DoubleIt req = (DoubleIt) payload;
                   if (req.getNumberToDouble() == 30) {
                      throw new ProtocolException(
                            "Doubling 30 is not allowed by the web service provider.");
                   }
                }
             } catch (JAXBException ex) {
                throw new ProtocolException(ex);
             }
          }
          return true;
       }
    
    }
    
    SOAPHandler.java:
    package service;
    
    import java.util.Set;
    
    import javax.xml.namespace.QName;
    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPHeader;
    import javax.xml.soap.SOAPMessage;
    import javax.xml.ws.ProtocolException;
    import javax.xml.ws.handler.MessageContext;
    import javax.xml.ws.handler.soap.SOAPMessageContext;
    
    import org.w3c.dom.NodeList;
    
    public class SOAPHandler implements javax.xml.ws.handler.soap.SOAPHandler<SOAPMessageContext> {
    
       @Override
       public Set<QName> getHeaders() {
          return null;
       }
    
       @Override
       public void close(MessageContext mc) {
       }
    
       @Override
       public boolean handleFault(SOAPMessageContext mc) {
          return true;
       }
    
       @Override
       public boolean handleMessage(SOAPMessageContext mc) {
          HandlerUtils.printMessageContext("Service SOAPHandler", mc);
          if (Boolean.FALSE.equals(mc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY))) {
             SOAPMessage sm = mc.getMessage();
    
             try {
                SOAPHeader sh = sm.getSOAPHeader();
    
                // Note in real use validity checking should be done
                // (really two terms present? namespaces? etc.)
                NodeList termNodes = sh.getElementsByTagName("term");
                mc.put("termOne", termNodes.item(0).getTextContent());
                mc.put("termTwo", termNodes.item(1).getTextContent());
                // default scope is HANDLER (i.e., not readable by SEI
                // implementation)
                mc.setScope("termTwo", MessageContext.Scope.APPLICATION);
             } catch (SOAPException e) {
                throw new ProtocolException(e);
             }
          }
          return true;
       }
    }
    
  6. Update the web service provider to use the handlers. Replace the DoubleItPortTypeImpl.java file from Step #6 of the DoubleIt tutorial with the below class. The handler configuration file (handlers.xml) is declared and the WebServiceContext is injected so the web service can read any APPLICATION-scoped properities created by the web service's handlers.

  7. Attach the JAX-WS handlers to the web service provider. We'll now need to create the handlers.xml file declared in the previous step. This configuration file's XSD is defined in the JSR 181 Specification (Version 2.0, Appendix B).

    For the required location of the handler chain configuration file, CXF follows the JSR 181 standard (see Sect. 4.6.1) in that relative paths are evaluated from the location of the compiled class, and absolute paths (e.g., "/handlers.xml") with respect to the base of the classpath. In the sample I used an absolute path for @HandlerChain and placed the config file in a new service/src/main/resources directory. Per Maven's Standard Directory Layout rules, files in the resources directory are automatically placed by Maven in the classpath root (i.e., WEB-INF/classes) when it creates the web service WAR file.

    handlers.xml file in service/src/main/resources:

    <handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
        <handler-chain>
           <handler>
               <handler-name>ServiceLogicalHandler</handler-name>
               <handler-class>service.LogicalHandler</handler-class>
           </handler>
           <handler>
               <handler-name>ServiceSOAPHandler</handler-name>
               <handler-class>service.SOAPHandler</handler-class>
           </handler>
        </handler-chain>
    </handler-chains>
    
  8. Redeploy the web service and test that the client and service provider handlers are working. After redeploying the service, run the SOAP client again to follow the output of the four SOAP calls made. The client side handler output you should see on the console window as before. As for the web service provider, if you're using Linux, the handler output should be viewable from $CATALINA_HOME/logs/catalina.{date}.out; if Windows, the console window that is hosting Tomcat. You may wish to comment out some of the printMessageContext calls made above to reduce the output. Also note because of the MAX_VALUE limitation of 200 enforced by the client-side logical handler, the web service query doubling 300 will top out at 400.

Notes:

  • You may find design and/or performance benefits using CXF-specific interceptors instead of JAX-WS defined handlers. For example, within CXF, SOAP handlers can carry a performance penalty, as SAAJ loads the entire message as a DOM object into memory, as CXF's Dan Kulp noted.

  • RedHat has a chapter on writing handlers in its Developing Applications Using JAX-WS guide.
  • See the Additional Resources section of my CXF interceptor article for simpler CXF-specific ways of modifying SOAP requests and responses.

Comments

Post a Comment: