A Handler Chain with Two Handlers

In the predictionsSOAP service, three of the operations require the id of a Prediction: the getOne, edit, and delete operations. The id is a positive integer. What happens if the client, through oversight or mischief, submits a negative integer or zero as the id? The service throws a SOAP fault. A SOAP request with a bad id is a waste of time and bandwidth, and avoiding such a request would be a gain in efficiency.

To guard against an invalid id, a client-side handler could inspect every outgoing SOAP message to determine if it has an id and, if so, whether the id is a positive integer. If the id is negative, the handler could substitute the absolute value and let the request continue on its way to the service; if the id is zero, the handler could throw an exception immediately and thereby short-circuit a request that is doomed to fail on the service side. The client-side SOAPHandler already in place could be amended to do this work, as the SOAP handler has access to the entire SOAP message; however, a LogicalHandler is better suited to the proposed task because the id is part of the payload in the SOAP request’s body. The existing SOAPHandler can be left as is and a LogicalHandler can be added to the handler chain. Modular design recommends this approach, which is also an opportunity to show a two-member handler chain in action.

There are two ways in which the LogicalHandler can access the payload of a SOAP message: as a native Java object generated with JAX-B or as an XML document. This example uses a Java object so that the id property can be inspected and perhaps changed using the familiar get/set idiom. If the id in the client request message is negative, a set-method then can be used to change the id value. If the id is zero, the handler can disable any further client-side handling and throw a fault to signal that the message cannot be fixed. With JAX-B in support, the processing is straightforward.

The IdHandler class (see Example 5-10) implements the LogicalHandler rather than the SOAPHandler interface (line 1). To implement this interface, the IdHandler needs to define close, handleFault, and handleMessage, but a LogicalHandler implementation, unlike a SOAPHandler implementation, does not define the getHeaders method precisely because a logical handler has no access to the SOAP headers. In this example, the close and handleFault methods are minimally defined.

Example 5-10. The client-side IdLogicalHandler

class IdHandler implements LogicalHandler<LogicalMessageContext> {                1
    public void close(MessageContext mctx) { }
    public boolean handleFault(LogicalMessageContext lmctx) {
        return true;
    }
    public boolean handleMessage(LogicalMessageContext lmctx) {
        Boolean outbound =
           (Boolean) lmctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outbound) { // request?
            LogicalMessage msg = lmctx.getMessage();
            try {
                JAXBContext jaxbCtx = JAXBContext.newInstance("clientSOAP");      2
                Object payload = msg.getPayload(jaxbCtx);                         3
                // Check payload to be sure it's what we want.
                if (payload instanceof JAXBElement) {
                    Object value = ((JAXBElement) payload).getValue();
                    // Three possibilities of interest: GetOne, Edit, or Delete
                    int id = 0;
                    boolean getOne, edit, delete;
                    getOne = edit = delete = false;
                    if (value.toString().contains("GetOne")) {                    4
                        id = ((GetOne) value).getArg0();
                        getOne = true;
                    }
                    else if (value.toString().contains("Edit")) {
                        id = ((Edit) value).getArg0();
                        edit = true;
                    }
                    else if (value.toString().contains("Delete")) {
                        id = ((Delete) value).getArg0();
                        delete = true;
                    }
                    else
                        return true; // GetAll or Create
                    // If id > 0, there is no problem to fix on the client side.
                    if (id > 0) return true;                                      5
                    // If the request is GetOne, Edit, or Delete and the id is zero,
                    // there is a problem that cannot be fixed.
                    if (getOne || edit || delete) {
                        if (id == 0) // can't fix
                            throw new RuntimeException("ID cannot be zero!");     6
                        // id < 0 and operation is GetOne, Edit, or Delete
                        int newId = Math.abs(id);
                        // Update argument.
                        if (getOne) ((GetOne) value).setArg0(newId);              7
                        else if (edit) ((Edit) value).setArg0(newId);
                        else if (delete) ((Delete) value).setArg0(newId);
                        // Update payload.
                        ((JAXBElement) payload).setValue(value);                  8
                        // Update message
                        msg.setPayload(payload, jaxbCtx);                         9
                    }
                }
            }
            catch(Exception e) { throw new RuntimeException(e); }
        }
        return true;
    }
}

The logic in the handleMessage method is a bit tricky. Like the ClientHashHandler, the IdHandler needs to work only on outgoing (that is, request) messages; hence, the IdHandler first checks on the direction of the SOAP message. The logical handler then extracts the payload from an outgoing message. The payload is available in two formats:

The extracted payload could be from any request. The next task, therefore, is to exclude requests that do not include an id. There are two such requests, one against the getAll operation and another against the create operation. The requests of interest are against the getOne, edit, and delete operations; an if construct, which starts on line 4, then determines whether the request is against one of these three operations. In summary, requests against the create and getAll operations are filtered out, and the remaining requests are inspected to determine if the outgoing id is a bad value.

If the outgoing message has an id, the handler extracts the id from the payload with a statement such as:

id = ((GetOne) value).getArg0();

There is one such statement for each of the three types: GetOne, Edit, and Delete. If the outgoing SOAP message is none of these, then true is returned from the else clause to signal that the IdHandler has finished its work and other handlers in the chain, if any, can begin theirs. Here is the code segment for review:

if (value.toString().contains("GetOne")) {
  id = ((GetOne) value).getArg0();
  getOne = true;
}
else if (value.toString().contains("Edit")) {
  id = ((Edit) value).getArg0();
  edit = true;
}
...
else
  return true; // GetAll or Create: no id

Once the payload’s id value has been extracted, there are three possible outcomes:

  • If the extracted id is a positive value, there is nothing more for the IdHandler to do and, therefore, the handler returns true (line 5), thereby indicating that other handlers in the chain, in any, can begin their processing. In fact, there is now another handler in the chain: the ClientHashHandler, which executes after the logical IdHandler. By the way, the id still may be out of bounds in the sense that no Prediction has such an id, but this is a problem that must be detected and managed on the service side, not the client side.
  • If the extracted id is zero, a RuntimeException is thrown (line 6) to terminate the entire request process because zero is not a legitimate value for an id. It makes no sense to send an id of zero to the predictionsSOAP service.
  • If the extracted id is negative (for instance, -31), then its absolute value (in this case, 31) is treated as the intended value. A set-method is invoked with the new id value (line 7), which updates the payload’s value. The payload itself is updated (line 8) and, finally, this newly edited payload is inserted as a replacement in the outgoing SOAP message (line 9).

This handler logic is clearly lower level than is the application logic of invoking service operations such as edit or getAll. JAX-WS is designed under the separation of concerns principle: low-level message inspection and tweaking occur at the handler level, whereas high-level operation invocations occur at the application level.

The IdHandler, like the ClientHashHandler, is a nonpublic class in the same file as the ClientHandlerResolver, a public class. The reason is convenience: one file holds all of the low-level artifacts. The handlers and the resolver all could be public classes and, accordingly, in their own files. The IdHandler, again like the ClientHashHandler, needs to be registered with the runtime. Here is the revised getHandlerChain method in the ClientHandlerResolver class:

public List<Handler> getHandlerChain(PortInfo portInfo) {
    List<Handler> handlerChain = new ArrayList<Handler>();
    handlerChain.add(new IdHandler());                             1
    handlerChain.add(new ClientHashHandler(this.name, this.key));  2
    return handlerChain;
}

The logical handler IdHandler is added to the chain (line 1) in front of the message handler ClientHashHandler (line 2) because, on an outgoing message, logical handlers execute before message handlers. Yet even if the order were reversed in this code, the runtime still would ensure that the logical IdHandler executed before the message handler ClientHashHandler. For handlers in the same group (e.g., logical handlers), the specified order in the getHandlerChain method matters; for handlers in different groups, as in this example, the runtime orders the execution so that all logical handlers execute before any message handlers do.

The handler examples illustrate the various ways in which SOAP messages can be inspected and manipulated. The client-side AwsSoapHandler, a message handler, adds elements to the body of a SOAP request, and the client-side ClientHashHandler, also a message handler, adds blocks to the header of a SOAP request. The logical handler IdHandler inspects the payload of a SOAP request and, under the right circumstances, edits this payload so that the request has a chance of succeeding with the service. The code in all three handlers is appropriately low-level, as befits code designed to be close to the SOAP metal.

There are three possible parts to a SOAP message: the header blocks, the body, and attachments. The first and the last are optional. The examples so far have examined the SOAP header and the SOAP body through code examples. The next section does the same for SOAP attachments.