The Handler Level in SOAP-Based Services and Clients

Figure 5-3 shows the structure of a SOAP message. In SOAP over HTTP, such a message is itself the body of either an HTTP request, typically a POST request, or an HTTP response. The message consists of:

The structure of a SOAP message

Figure 5-3. The structure of a SOAP message

SOAP attachments are covered later in this chapter. For now, the distinction between the SOAP header and the SOAP body is of interest because this difference is a way to focus on two related but separate SOAP APIs: an API for what might be called the application level and a separate API for what might be called the handler level. (This distinction roughly mirrors, in Java-based websites, the distinction between the servlet API and the filter API, although servlets and filters occur only on the server side.) The application and handler levels occur on the service side and on the client side. In the examples so far, the focus has been on the application level with the use of annotations such as @WebService and @WebMethod. The Amazon E-Commerce clients in Chapter 4 touched on the handler level because a hash generated from the user’s secretKey together with a timestamp had to be inserted in a specified location within a SOAP message; a handler did the required work. This section introduces the handler level in two steps: first, by examining the AwsHandlerResolver class used in the two SOAP-based E-Commerce clients of Chapter 4, and second, with an example that has code at the handler level on both the client and the service side.

A quick look at how the application and handler levels interact may be useful before a full code example. Recall the RandClient from Chapter 4, in particular these three lines of code:

RandServiceService service = new RandServiceService();
RandService port = service.getRandServicePort();
System.out.println(port.next1());  // invoke next1

The first two lines set up the call to next1, which returns a randomly generated integer. When this call executes, the underlying SOAP libraries generate the appropriate SOAP envelope and send it, over HTTP, to the RandService. The three lines of code are at the application level. The RandClient does not use any handlers; hence, nothing happens at the handler level. Suppose that there was a handler; for now, the code details can be ignored, but a depiction (see Figure 5-4) may be helpful.

A handler is inherently bidirectional: on either side, client or service, a handler handles incoming and outgoing messages. A handler can detect whether a given SOAP message is coming in or going out and react accordingly. Figure 5-4 depicts a single handler, but multiple ones, a handler chain, are possible on either side. The service side in the figure has no handlers.

The RandClient with an assumed handler

Figure 5-4. The RandClient with an assumed handler

Handlers come in two types:

A SOAPHandler can do anything that a LogicalHandler can do because the former has access to the entire SOAP message, but a LogicalHandler is convenient if the handler code requires access only to the payload in the body of a SOAP message. Examples of both LogicalHandler code and SOAPHandler code are forthcoming. How handlers of different types can be chained together also is covered in the examples.

Handlers are close to the metal in that they have access to either the entire SOAP message or just the payload in the body. In Figure 5-4, the SOAP libraries that underlie the RandClient generate a SOAP request message in calls to next1 or nextN. By default, these libraries create a message without a SOAP header. The message then goes to the handler before being handed over to HTTP for transport to the service. The handler can perform arbitrary logic on the SOAP message. For example, the handler could add a SOAP header if the SOAP message did not have one already. The handler could add, edit, remove, and otherwise process elements in the SOAP message—header, body, and attachments. JAX-WS confines such low-level processing to the handler level so that the application level remains a high level at which service operations are invoked and response values are processed. In short, the handler/application distinction represents a separation of concerns.

The AwsHandlerResolver and AwsSoapHandler classes (see Example 5-1) make up the handler-level code mentioned but not explained in Chapter 4.

Example 5-1. The AwsHandlerResolver and AwsSoapHandler classes

package amazon;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.PortInfo;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class AwsHandlerResolver implements HandlerResolver {
    private String awsSecretKey;

    public AwsHandlerResolver(String awsSecretKey) {
        this.awsSecretKey = awsSecretKey;
    }
    public List<Handler> getHandlerChain(PortInfo portInfo) {
        List<Handler> handlerChain = new ArrayList<Handler>();
        QName serviceQName = portInfo.getServiceName();
        if(serviceQName.getLocalPart().equals("AWSECommerceService")) {
            handlerChain.add(new AwsSoapHandler(awsSecretKey));
        }
        return handlerChain;
    }
}

class AwsSoapHandler implements SOAPHandler<SOAPMessageContext> {
    private byte[ ] secretBytes;

    public AwsSoapHandler(String awsSecretKey) {
        secretBytes = getBytes(awsSecretKey);
    }
    public void close(MessageContext mCtx) { }
    public Set<QName> getHeaders() { return null; }
    public boolean handleFault(SOAPMessageContext mCtx) {
        return true;
    }
    public boolean handleMessage(SOAPMessageContext mCtx) {
        Boolean outbound =
           (Boolean) mCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outbound) {
            try {
                SOAPMessage soapMessage = mCtx.getMessage();
                SOAPBody soapBody = soapMessage.getSOAPBody();
                Node firstChild = soapBody.getFirstChild(); // operation name
                String timeStamp = getTimestamp();
                String signature = getSignature(firstChild.getLocalName(),
                                                timeStamp,
                                                secretBytes);
                append(firstChild, "Signature", signature);
                append(firstChild, "Timestamp", timeStamp);
            }
            catch(Exception e) {
                throw new RuntimeException("SOAPException thrown.", e);
            }
        }
        return true; // continue down the handler chain
    }
    private String getSignature(String operation, String timeStamp,
                                byte[ ] secretBytes) {
        try {
            String toSign = operation + timeStamp;
            byte[] toSignBytes = getBytes(toSign);
            Mac signer = Mac.getInstance("HmacSHA256");
            SecretKeySpec keySpec = new SecretKeySpec(secretBytes, "HmacSHA256");
            signer.init(keySpec);
            signer.update(toSignBytes);
            byte[ ] signBytes = signer.doFinal();
            String signature = new String(Base64.encodeBase64(signBytes));
            return signature;
        }
        catch(Exception e) { throw new RuntimeException(e); }
    }
    private String getTimestamp() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat dateFormat =
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        return dateFormat.format(calendar.getTime());
    }
    private void append(Node node, String elementName, String elementText) {
        Element element = node.getOwnerDocument().createElement(elementName);
        element.setTextContent(elementText);
        node.appendChild(element);
    }
    private byte[ ] getBytes(String str) {
        try {
            return str.getBytes("UTF-8");
        }
        catch(Exception e) { throw new RuntimeException(e); }
    }
}

The code in the AwsHandlerResolver class registers handlers with the runtime, and the AwsSoapHandler is the message rather than a logical handler that gets registered. The handler API is event-driven: the application registers a handler chain, which may be a chain with just one handler, with the Java runtime; the runtime then invokes the appropriate handler methods on incoming and outgoing messages. The handler itself can detect a message’s direction and react accordingly.

In the E-Commerce clients of Chapter 4, the AwsHandlerResolver class is used in this context (line 1):

AWSECommerceService service = new AWSECommerceService();
service.setHandlerResolver(new AwsHandlerResolver(secretKey));  1
AWSECommerceServicePortType port = service.getAWSECommerceServicePort();

Line 1 links the service with an AwsHandlerResolver instance that encapsulates the user’s secretKey. As the name suggests, the AwsHandlerResolver resolves which handlers, if any, are to be in play.

To implement the HandlerResolver interface, a class must define one method:

public List<Handler> getHandlerChain(PortInfo portInfo);

In this case, the implementation is short because there is only one handler to put into the chain:

public List<Handler> getHandlerChain(PortInfo portInfo) {
   List<Handler> handlerChain = new ArrayList<Handler>();          1
   QName serviceQName = portInfo.getServiceName();
   if (serviceQName.getLocalPart().equals("AWSECommerceService"))  2
      handlerChain.add(new AwsSoapHandler(awsSecretKey));          3
   return handlerChain;
}

The getHandlerChain method creates an empty List<Handler> (line 1) and then checks whether the service in question is Amazon’s AWSECommerceService, the official name for the E-Commerce service (line 2). If so, an instance of the AwsSoapHandler class, initialized with the user’s secretKey, is constructed (line 3). The Java runtime now ensures that the handlers in the list, in this case just one, are invoked after the SOAP message has been built but before this message is handed off to HTTP for transport to the service.

The handler class AwsSoapHandler, which the resolver registers with the runtime, is a SOAPHandler rather than a LogicalHandler:

class AwsSoapHandler implements SOAPHandler<SOAPMessageContext> {
 ...

The reason is that the AwsSoapHandler needs to add elements to the message body, and a LogicalHandler provides access only to the current payload in the body. The methods in the AwsSoapHandler class add to this initial payload. To implement the SOAPHandler interface and its superinterface Handler, the AwsSoapHandler class must define four methods:

getHeaders
This is a convenience method that gives the handler access to the SOAP header elements or headers for short. The runtime invokes this method first. In the current example, the method is minimally defined but not used. Later examples use the getHeaders method.
close
As the name suggests, this method is the last one that the runtime invokes. Once again, the current example minimally defines but does not use this method.
handleMessage and handleFault
The runtime invokes exactly one of these. For example, the two E-Commerce clients in Chapter 4 send a search request to the Amazon service with one of two results: the Amazon service accepts the request, conducts the search, and returns the results; or the Amazon service generates a SOAP fault. Either a standard SOAP response message or a fault message returns to the client: if a standard response, then the runtime invokes handleMessage in the handler; if a SOAP fault, then the runtime invokes handleFault in the handler.

The return type for the methods handleMessage and handleFault is boolean. A return value of true means continue executing other handlers, if any, in the chain; a return value of false means do not execute other handlers, if any, in the chain. The logic is similar to the logic of filters in servlet-based websites. In this example, handleFault is minimally defined (that is, the method simply returns true), but handleMessage has logic to make the SOAP request comply with the requirements of the E-Commerce service.

The handleMessage method deserves a closer look. Here is the main part of the code, slightly reformatted:

Boolean outbound = (Boolean) mCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outbound) {                                                      1
   try {
      SOAPMessage soapMessage = mCtx.getMessage();
      SOAPBody soapBody = soapMessage.getSOAPBody();                 2
      Node firstChild = soapBody.getFirstChild(); // operation name  3
      String timeStamp = getTimestamp();
      String signature = getSignature(firstChild.getLocalName(),
                                      timeStamp,
                                      secretBytes);
      append(firstChild, "Signature", signature);                    4
      append(firstChild, "Timestamp", timeStamp);                    5
   }
   ...

The handleMessage method first checks, in line 1, whether it is being invoked on an outgoing (that is, request) or incoming (that is, response) SOAP message. Incoming messages are of no interest to this handler. For an outgoing or request SOAP message, the method uses the mCtx argument (of type SOAPMessageContext) to get the full SOAP message from which the SOAPBody is extracted (line 2). The first child in the SOAP body is the wrapper element, the name of the E-Commerce service operation (in this example, the search operation), because the E-Commerce service uses wrapped document style (line 3). Two utility methods provide the current time in the required format and an HmacSHA256 digest generated from the user’s secretKey and other information (lines 4 and 5). These low-level operations are not of particular interest right now, but they are required in any SOAP-based request to E-Commerce service. (Chapter 6, on security, goes into the details of a hash such as HmacSHA256, and the next example in this chapter clarifies HmacSHA256 further.)

Once the required timestamp and hash value are in hand, the handleMessage method appends two XML elements to the wrapper element in the SOAP body: one with the so-called signature, which is the message digest or hash value, and another with the timestamp. After handleMessage returns true, the runtime invokes the close method—and then hands the amended SOAP message off to the HTTP transport. Example 5-2 depicts the before and after situation with respect to the work of the AwsSoapHandler: this handler adds lines 1 and 2 to the already created SOAP request message.

Example 5-2. A before/after depiction of how the AwsSoapHandler works

The SOAP-based version of Amazon’s E-Commerce service enforces strict conditions on the structure of a SOAP request message. In particular, this service requires that a message digest (the signature) and a timestamp be in the request body as children of the wrapper element.

JAX-WS handlers are a way to separate low-level concerns, which require inspection and even manipulation of SOAP messages, from the high-level concerns of invoking, as transparently as possible, web service operations. At the application level, the SOAP is completely hidden; at the handler level, the SOAP is exposed for whatever processing is required. The next example uses handlers on the client and on the service side; this example also introduces SOAP faults, which can be thrown at either the application or the handler level.