Get a handle on the JAX-WS API's handler framework

Learn how to use the handler framework in your Web services projects

1 2 3 Page 2
Page 2 of 3

Developing handlers in JAX-WS

As is clear from the handler class hierarchy diagram, all handlers in JAX-WS must implement the following three methods.

  • handleMessage( ): Called for inbound and outbound messages.
  • handleFault( ): Called instead of handleMessage( ) when the message contains a protocol fault.
  • close( ): Called after completion of message processing by all handlers after the completion of a message exchange pattern (MEP).

JAX-WS specifies in clear terms the semantics of the handleMessage() and handleFault() methods both for the return values and various exceptional scenarios. The close() method may be used to clean up any resources created during message processing.

In addition, a SOAPHandler also must implement the getHeaders() method, which should return header blocks' QNames, with each QName representing the qualified name of the outermost element of one header block.

For applications or JAX-WS runtimes to manage a handler's lifecycle, the handler can annotate a method with the @PostConstruct annotation so it will be called after the handler is created in the runtime. The @PreDestroy annotation allows the method to be called before the handler is destroyed. All those annotated methods must return void and take no arguments.

Let's now look at the handlers in our example application.

First, the abstract class BaseHanlder<T extends MessagetContext> provides simple implementations for close() and handleFault(). All handlers in the example application extend from this base class. In addition, this base handler also comes with two methods init() and destroy(), annotated for managing the handler's lifecycle. Those methods print notifications to notify readers when those lifecycle methods are called. For example, at the service side, the method annotated with @PostConstruct is called when the handler is deployed, while un-deploying the handler will cause the method annotated with @PreDestroy to be called.

Here is the BaseHandler<T extends MessageContext> class:

public class BaseHandler<T extends MessageContext> {
   protected String HandlerName = null;

   protected PrintStream out = System.out;

   @PostConstruct
   public void init() {
      out.println("------------------------------------");
      out.println("In Handler " + HandlerName + ":init()");
      out.println("Exiting Handler " + HandlerName + ":init()");
      out.println("------------------------------------");
   }

   @PreDestroy
   public void destroy() {
      out.println("------------------------------------");
      out.println("In Handler " + HandlerName + ":destroy()");
      out.println("Exiting Handler " + HandlerName + ":destroy()");
      out.println("------------------------------------");
   }


   public boolean handleFault(T mc) {
      out.println("------------------------------------");
      out.println("In Handler " + HandlerName + ":handleFault()");
      out.println("Exiting Handler " + HandlerName + ":handleFault()");
      out.println("------------------------------------");
      return true;
   }
   
   public void close(MessageContext mc) {
      out.println("------------------------------------");
      out.println("In Handler " + HandlerName + ":close()");
      out.println("Exiting Handler " + HandlerName + ":close()");
      out.println("------------------------------------");
   }

   public void setHandlerName(String handlerName) {
      HandlerName = handlerName;
   }
   
}

Our next handler is the EnvelopeLoggingSOAPHandler, a SOAP handler that logs the message in exchange to system output stream. This snippet captures the major part of this class:

public class EnvelopeLoggingSOAPHandler 
   extends BaseHandler<SOAPMessageContext> 
   implements SOAPHandler<SOAPMessageContext> {
    
      private static PrintStream out = System.out;
      private static final String HANDLER_NAME = "EnvelopeLoggingSOAPHandler";
     
      public EnvelopeLoggingSOAPHandler(){
         super();
         super.setHandlerName(HANDLER_NAME);        
      }
    
      public boolean handleMessage(SOAPMessageContext smc) {
         out.println("------------------------------------");
         out.println("In SOAPHandler " + HandlerName + ":handleMessage()");
         logToSystemOut(smc);
         out.println("Exiting SOAPHandler " + HandlerName + ":handleMessage()");
         out.println("------------------------------------");    
        return true;
      }

      private void logToSystemOut(SOAPMessageContext smc) {
         Boolean outboundProperty = (Boolean)
           smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        
         if (outboundProperty.booleanValue()) {
            out.println("\ndirection = outbound ");
        } else {
           out.println("\ndirection = inbound ");
        }
        
        SOAPMessage message = smc.getMessage();
        try {
           out.println("");   
           message.writeTo(out);
           out.println("");   
        } catch (Exception e) {
           out.println("Exception in SOAP Handler #1: " + e);
        }
   }

......
}

We identify the message direction by checking the standard property MESSAGE_OUTBOUND_PROPERTY on the SOAPMessageContext passed into handleMessge(), and retrieve the message in exchange by calling getMessage() on this same object.

Message direction is relative to client application on the client side, and to service endpoint implementation at the server side. This important information decides what is available in the message context (such as the properties HTTP_REQUEST_HEADERS or HTTP_RESPONSE_HEADERS), and whether the message contained in the message context is for request or for response. Handlers may need to implement different logic for messages of different directions.

A SOAPMessageContext's getMessage() method provides the whole SOAP envelope (both SOAP header and body parts). The same method on a LogicalMessageContext object returns only the payload of the message in exchange. In the SOAP binding case, this means LogicalMessageContext's getMessage() only returns the SOAP body part.

Another handler, JAXPPayloadLoggingLogicalHandler, also logs the message in exchange, but it is a logical handler, and therefore, can only print the SOAP body part. The following snippet captures the main part of this class:

public class JAXPPayloadLoggingLogicalHandler 
extends BaseHandler<LogicalMessageContext> 
implements LogicalHandler<LogicalMessageContext> {
   private static PrintStream out = System.out;

   private static final String HANDLER_NAME = "JAXPPayloadLoggingLogicalHandler";

   public JAXPPayloadLoggingLogicalHandler() {
      super();
      super.setHandlerName(HANDLER_NAME);
   }

   public boolean handleMessage(LogicalMessageContext smc) {
      out.println("------------------------------------");
      out.println("In LogicalHandler " + HandlerName + ":handleMessage()");

      logToSystemOut(smc);

      out.println("Exiting LogicalHandler " + HandlerName
            + ":handleMessage()");
      out.println("------------------------------------");

      return true;
   }

   private void logToSystemOut(LogicalMessageContext smc) {
      Boolean outboundProperty = (Boolean) smc
            .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

      if (outboundProperty.booleanValue()) {
         out.println("\ndirection = outbound ");
      } else {
         out.println("\ndirection = inbound ");
      }

      LogicalMessage message = smc.getMessage();

      try {
         Source src = message.getPayload();
         out.println(HandlerUtil.getXMLFromSource(src));
      } catch (Exception e) {
         out.println("Exception in " + HandlerName + ":" + e);
      }
   }
}

The getMessage() method on LogicalMessageContext returns a LogicalMessage object. With LogicalMessage, we can work with both javax.xml.transform.Source or a JAXBContext.

With the JAXPPayloadLoggingLogicalHandler, we retrieve the payload as a Source object; and with another handler, JAXBPayloadLoggingLogicalHandler, we input a JAXBContext object into the LogicalMessage's getMessage() method and marshal the payload into domain objects in Java.

We can also modify the payload with a LogicalMessage object in logical handlers using a Source object, or with the help of a JAXB context.

Working with message contexts

The examples in the previous section demonstrate how to work with MessageContext in handlers. This class can also be used directly in the service implementation class and indirectly in the client application.

A service endpoint implementation may obtain the MessageContext object through a WebServiceContext. The service runtime will inject the WebServiceContext on any field marked with @Resource. To get a MessageContext instance, we can call the WebServiceContext's getMessageContext() method. For an example, here is a snippet from CreditCardServiceImpl.java:

public class CreditCardServiceImpl implements CardServicePortType {
   private static Random rdm;
   ......
    
   @Resource
   WebServiceContext wsContext;

   public AuthorizationStatus authorizePayment(AuthorizationRequest request) throws CardServiceException {
      try {
         MessageContext msgContext = wsContext.getMessageContext();         
         Boolean authnStatus = (Boolean)msgContext.get("authnStatus");

         ......
         if (authnStatus != null) cardInfo.append("Authentication Status as Reported by
            ServiceAuthentricationSOAPHandler is:" + authnStatus.toString() + "\n");
         cardInfo.append("********************************************** \n");
         System.out.println(cardInfo.toString());

         ......
      } catch (Exception e) {
         ......
      }
   }

   ......
}

The code tries to retrieve a property called authnStatus from the MessageContext and print it to the system output stream. We set the property in ServiceAuthenticationSOAPHandler.java as follows:

public class ServiceAuthenticationSOAPHandler extends
      BaseHandler<SOAPMessageContext> implements
      SOAPHandler<SOAPMessageContext> {
   ......

   public boolean handleMessage(SOAPMessageContext smc) {
      boolean exit = false;
      out.println("------------------------------------");
      out.println("In SOAPHandler " + HandlerName + ":handleMessage()");

      boolean direction = ((Boolean) smc
            .get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY))
            .booleanValue();
      if (direction) {
         out.println("direction = outbound");
      } else {
         out.println("direction = inbound");

         // ......
         try {
            // get SOAP envelope from SOAP message
            SOAPEnvelope se = smc.getMessage().getSOAPPart().getEnvelope();

            // get the headers from envelope
            SOAPHeader sh = se.getHeader();
            if (sh == null) {
               out.println("--- No headers found in the input SOAP request");
               exit = false;
            } else {
               // call method to process header
               exit = processSOAPHeader(sh);
            }
         } catch (Exception ex) {
            ex.printStackTrace();
            exit = false;
         }
         // ......
      }

      out.println("Exiting SOAPHandler " + HandlerName + ":handleMessage()");
      out.println("------------------------------------");
      
 if (exit){
         smc.put(AuthenticationHandlerConstants.AUTHN_STAUTS, true);
      }else{
         smc.put(AuthenticationHandlerConstants.AUTHN_STAUTS, true);
      }      
      smc.setScope(AuthenticationHandlerConstants.AUTHN_STAUTS, MessageContext.Scope.APPLICATION);      
      return exit;
   }

   ......
}

Note the property is scoped to APPLICATION, because only APPLICATION-scoped properties are visible to service implementation classes. Here, the AuthenticationHandlerConstants.AUTHN_STAUTS denotes the string authnStatus. We use the string directly in the service implementation class to prevent this class from depending on the interface AuthenticationHandlerConstants, which is part of the jaxws-handler project.

With a MessageContext instance available, the service implementation class may also send information to the handlers deployed in the service runtime. The diagram below depicts the interaction:

Figure 3. MessageContext, handler and service implementation class. Click on thumbnail to view full-sized image.

A client application can configure metadata through the request context and response context obtained from the BindingProvider interface (an instance of port proxy or Dispatch). The request and response contexts are of type java.util.Map<String,Object> and are returned with the getRequestContext() and getResponseContext() methods.

The contents of the request context are used to initialize the message context prior to invoking any handlers for the outbound message. Each property within the request context is copied to the message context with a scope of HANDLER. The message context's contents are used to initialize the response context after invoking any handlers for an inbound message. The response context is first emptied, and then each property in the message context that has a scope of APPLICATION is copied to the response context. This figure captures the interaction graphically:

Figure 4. MessageContext, RequestContext, ResponseContext, handler and Web service client. Click on thumbnail to view full-sized image.

The following code snippet from CardServiceClient.java in jaxws-handler-client1 shows how to access and update a request context with a port proxy:

public class CardServiceClient {
   public static void main(String[] args) {
      try {          
            QName serviceName = new QName("http://cardservice.handler.jaxws.company.com/service", "CardService");
           URL wsdlLoc = new URL("http://localhost:8484/creditcard/CardService?wsdl");
           CardService service = new CardService(wsdlLoc, serviceName);
         CardServicePortType port = service.getCardServicePort();
         
         Map<String, Object> requestContext = ((BindingProvider)port).getRequestContext();
         requestContext.put("authn_userid", "yyang");
         requestContext.put("authn_password", "mypassword");
……
      } catch (Exception e) {
         e.printStackTrace();
      }
   }   
}

The client casts the port proxy to BindingProvider, obtains the request context from this interface, and sets a pair of userid and password into the context. CardServiceClient.java in jaxws-handler-client2 uses similar code to work with a Dispatch instance and achieves the same purpose.

The ClientAuthenticationSOAPHandler below demonstrates how a handler can pick up this userid and password pair set in the request context by a client:

public class ClientAuthenticationSOAPHandler …… {
   ……

   public boolean handleMessage(SOAPMessageContext smc) {
      out.println("------------------------------------");
      out.println("In SOAPHandler " + HandlerName + ":handleMessage()");

      boolean direction = ((Boolean) smc
            .get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY))
            .booleanValue();

      String USERID = (String) smc
            .get(AuthenticationHandlerConstants.REQUEST_USERID);
      String PASSWORD = (String) smc
            .get(AuthenticationHandlerConstants.REQUEST_PASSWORD);

      if (direction) {
         out.println("direction = outbound");

         // ......
         try {

            /*
             * add a header with the authentication info into the SOAP
             * message:
             * 
             * <env:Header> <nsAuthen:authnHeader
             * xmlns:nsAuthn="http://cardservice.handler.jaxws.company.com/authn">
             * <nsAuthn:id>yyang</nsAuthn:id> <nsAuthn:password>mypassword</nsAuthn:password>
             * </nsAuthn:authnHeader> </env:Header>
             */

            // get SOAP envelope from SOAP message
            SOAPEnvelope envelope = smc.getMessage().getSOAPPart()
                  .getEnvelope();

            // create instance of SOAP factory
            SOAPFactory soapFactory = SOAPFactory.newInstance();

            // create SOAP elements specifying prefix and URI
            SOAPElement headerElm = soapFactory.createElement(
                  AuthenticationHandlerConstants.AUTHN_LNAME,
                  AuthenticationHandlerConstants.AUTHN_PREFIX,
                  AuthenticationHandlerConstants.AUTHN_URI);
            SOAPElement idElm = soapFactory.createElement("id",
                  AuthenticationHandlerConstants.AUTHN_PREFIX,
                  AuthenticationHandlerConstants.AUTHN_URI);

            // attach value to id element
            idElm.addTextNode(USERID);
            SOAPElement passwdElm = soapFactory.createElement("password",
                  AuthenticationHandlerConstants.AUTHN_PREFIX,
                  AuthenticationHandlerConstants.AUTHN_URI);

            // attach value to password element
            passwdElm.addTextNode(PASSWORD);

            // add child elements to the root element
            headerElm.addChildElement(idElm);
            headerElm.addChildElement(passwdElm);

            // create SOAPHeader instance for SOAP envelope
            SOAPHeader sh = envelope.addHeader();

            // add SOAP element for header to SOAP header object
            sh.addChildElement(headerElm);
         } catch (Exception ex) {
            ex.printStackTrace();
         }
      } else {
         out.println("direction = inbound");
      }
      // ......

      out.println("Exiting SOAPHandler " + HandlerName + ":handleMessage()");
      out.println("------------------------------------");

      return true;
   }

……
}

The two lines of interest are:

String USERID = (String) smc
            .get(AuthenticationHandlerConstants.REQUEST_USERID);
      String PASSWORD = (String) smc
         .get(AuthenticationHandlerConstants.REQUEST_PASSWORD);

In a typical use-case, the handler inserts the information as SOAP headers to convey the same information to service-side (SOAP) handlers or the service-endpoint implementation. This is exactly what the above code snippet does. In our example application, at the service side, a ServiceAuthenticationSOAPHandler retrieves the userid and password pair from the SOAP header, and uses it to authenticate the request.

Authenticating Web service requests using user name and password as we do with ClientAuthenticationSOAPHandler and ServiceAuthenticationSOAPHandler is obviously for the purpose of understanding the concepts only. Real applications need to address a whole set of other concerns and should follow the protocol defined by the Web Services Security standard.

This simple authentication implementation shows how states and parameters are passed into handlers through MessageContext from the context of an application client request. The ClientPerformanceMonitorLogicalHandler.java and CardServiceClient.java from jaxws-handler-client1 demonstrates how information transfers in the opposite direction, where an APPLICATION-scoped property set by a handler is passed to the client with MessageContext as the carrier. The property in consideration is ELAPSED_TIME, which ClientPerformanceMonitorLogicalHandler puts into the LogicalMessgeContext. The CardServiceClient picks it up from the response context and prints it to the system output stream.

1 2 3 Page 2
Page 2 of 3