Glen Mazza's Weblog

« Adding JAX-WS handle... | Main | Compressing SOAP... »

https://glenmazza.net/blog/date/20180304 Sunday March 04, 2018

Replacing JAX-WS handlers with Apache CXF interceptors

This article shows how to use Apache CXF interceptors as an alternative to JAX-WS handlers for your CXF-based web service providers and SOAP clients. The finished tutorial source code can be downloaded or cloned from GitHub.

Prior to beginning with CXF interceptors, if your goal is just to modify the SOAP requests and/or responses, check the Additional Resources section at the bottom of this blog entry. It lists simpler alternatives to working with CXF interceptors that might also accomplish what you're looking for.

Interceptor architectural overview

Apache CXF internally uses constructs called interceptors and interceptor chains in its processing of SOAP requests and responses. An interceptor chain is a sequence of interceptors, activated by CXF's PhaseInterceptorChain class, each of which processes a SOAP message in the manner of the Pipes and Filters messaging pattern. There are six such chains, although not all will be activated for every SOAP request/response:

  • outgoing and incoming from the perspective of the CXF SOAP client
  • incoming and outgoing from the CXF SOAP server
  • an outgoing SOAP fault chain from the CXF server
  • an incoming SOAP fault chain from the CXF client

A simple way to become acquainted with configuring interceptors is to activate logging of SOAP requests and/or responses, a process which is handled in CXF by interceptors. To learn how to program your own interceptors, the CXF source for its many internal interceptors is an excellent resource -- they can be searched for individually in the repository search box on the CXF GitHub home page.

Similarities and Differences between JAX-WS Handlers and CXF Interceptors

JAX-WS handlers are internally implemented in CXF by use of interceptors, so anything that can be done with the former can be done with the latter. Handlers and interceptors have strong similarities in that both have handleMessage() and handleFault() methods where needed processing can be added. Both provide mechanisms for SOAP messages as well as more generic XML over HTTP routing. The routing direction is also similar in that handleFault(), when errors occur, causes the interceptors to run in reverse order.

Some differences:

  • Separate interceptors need to be created for the request and the response, while, with JAX-WS handlers, the same handlers are activated both on request and reply.
  • Interceptors are not divided into "logical" and "protocol" types like handlers are (all interceptors are of the latter type).
  • An additional close() method (used for object cleanup) is available with handlers but not interceptors.
  • The standard Fault exception is different, for JAX-WS Handlers you have ProtocolException (or a subclass), while Interceptors use org.apache.cxf.interceptor.Fault.
  • Interceptors are ordered not only with respect to each other but also by the processing phase in which they are to occur. There are approximately 15 different phases in each direction that interceptors can be attached to.
  • Interceptors do not have the HANDLER/APPLICATION property scoping that JAX-WS Handlers do--all properties have APPLICATION scope, meaning that the client and service endpoints have access to all properties created within the interceptors. (There is a somewhat clumsy workaround available however if you must have HANDLER-scoped properties.)
  • Finally, there can be performance benefits as well with interceptors, as a CXF team member noted, due to interceptors tending to use more optimal XML processing techniques.

Important APIs for the JAX-WS handler objects:

Handler TypeHandler ClassMessage ContextMessage Class
Basic InterfaceHandler MessageContext N/A
Logical Handler (SOAP body without header or basic XML over HTTP)LogicalHandler LogicalMessageContext LogicalMessage
(SOAP) Protocol Handler (full SOAP envelope including header)SOAPHandler SOAPMessageContext SOAPMessage

Approximate equivalents for the CXF interceptors:

Interceptor TypeInterceptor ClassMessage Class
Basic InterfaceInterceptor Message
Logical InterceptorsAbstractPhaseInterceptor Message (XMLMessage a possible option)
SOAP InterceptorsAbstractSoapInterceptor SoapMessage

The main internal CXF classes which handle the interceptor Message to JAX-WS handler MessageContext translation are WrappedMessageContext, AbstractJAXWSMethodInvoker, JAXWSMethodInvoker, and JaxWsClientProxy.

Using CXF interceptors in place of JAX-WS handlers

Follow along the JAX-WS Handler tutorial for this process, except replace certain steps as shown below:

  • Skip Step #2 (HandlerUtils.java class), as it is handler-specific.

  • For Step #3, use the following interceptors class instead:

    Although we're using SAAJ above to add SOAP headers JAXB may be also be available for you--see this example.

  • For Step #4, Use the following SOAP client:

  • For Step #5, Use the following service interceptor classes in the service subproject instead:

    ValueCheckInInterceptor.java:
    package service;
    
    import java.util.List;
    
    import org.apache.cxf.interceptor.Fault;
    import org.apache.cxf.message.Message;
    import org.apache.cxf.phase.AbstractPhaseInterceptor;
    import org.apache.cxf.phase.Phase;
    import org.example.schema.doubleit.DoubleIt;
    
    public class ValueCheckInInterceptor extends AbstractPhaseInterceptor<Message> {
    
        public ValueCheckInInterceptor() {
            super(Phase.USER_LOGICAL);
        }
    
        @SuppressWarnings("unchecked")
        public void handleMessage(Message message) throws Fault {
            List<Object> myList = message.getContent(List.class);
    
            for (Object item : myList) {
                if (item instanceof DoubleIt) {
                    DoubleIt req = (DoubleIt) item;
                    if (req.getNumberToDouble() == 30) {
                        throw new Fault(
                           new Exception(
                              "Doubling 30 is not allowed by the web service provider."));
                    }
                }
            }
        }
    }
    
  • ReadSOAPHeaderInInterceptor.java:
    package service;
    
    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPHeader;
    import javax.xml.soap.SOAPMessage;
    
    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;
    import org.w3c.dom.NodeList;
    
    public class ReadSOAPHeaderInInterceptor extends AbstractSoapInterceptor { 
    
        public ReadSOAPHeaderInInterceptor() {
            super(Phase.USER_PROTOCOL);
        }
    
        @Override
        public void handleMessage(SoapMessage message) throws Fault {
            SOAPMessage sm = message.getContent(SOAPMessage.class);
    
            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");
                message.put("termOne", termNodes.item(0).getTextContent());
                message.put("termTwo", termNodes.item(1).getTextContent());
                /* JAX-WS Handler "setScope()" (HANDLER/APPLICATION) 
                   not available with interceptors, APPLICATION is standard
                   meaning both properties readable by service bean */
            } catch (SOAPException e) {
                throw new Fault(e);
            } 
        }
    }
    
  • For Step #6, use the following web service provider:

  • Step #7 can be skipped because I'm using annotations above to add the service-side interceptors--see here for alternative XML file configuration.

Working With Phases

The phases that your interceptors are attached to, as well as the ordering of interceptors within phases, are important for the successful execution of SOAP calls and responses. It can take some trial and error to determine this information for each of your interceptors. Some general advice:

  1. Pay attention to phase names in the IN and OUT chains, they are mostly different and ordered differently. If you attach your interceptor to a phase not applicable for the chain, it will not run. Also, when having your interceptor run after another interceptor (addAfter()), make sure that other interceptor will indeed be activated for that particular phase.
  2. If you wish to work with SAAJ, as we're doing above in adding and reading SOAP headers, make sure you first attach the SAAJInInterceptor or SAAJOutInterceptor as appropriate (as shown in the SOAP client and web service provider code.)
  3. If adding SOAP Headers in an interceptor (as with AddSOAPHeaderOutInterceptor), be sure to attach it after the SoapOutInterceptor as the latter creates the necessary SOAP Envelope objects first.
  4. If you wish to work with JAXB objects, as we do above with the two ValueCheck interceptors, make sure to choose a phase before marshalling (out-interceptor) or after unmarshalling (in-interceptor). (Marshalling, in JAXB-speak, is moving from Java objects to XML, unmarshalling is the reverse.)
  5. If throwing exceptions from a service in-interceptor, with the goal of stopping the SOAP call (as we do above in the service-side ValueCheckInInterceptor when you try to double 30), you will need to do so before the PRE_INVOKE phase--that phase is the point of no return for SOAP requests.
  6. When trying to order your own interceptors around CXF's internal ones, you may need to look at the latter's source code to determine the phase that they run in.
  7. If stumped on any particular issue, the CXF User's list or Stack Overflow can be of help.

Additional Resources

Comments

Post a Comment: