Skip to main content

skip to main content

developerWorks  >  SOA and Web services | XML  >

Web services programming tips and tricks: Extend JAX-RPC Web services using SOAP headers

Process SOAP 1.1 headers with JAX-RPC 1.0 SOAP handlers

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Richard Sitze (rsitze@us.ibm.com), Advisory Software Engineer, IBM Software Group

28 Apr 2004

In this article, the author examines how JAX-RPC SOAP handlers process SOAP message headers. Specifically, he shows how a handler adds a SOAP header to an outgoing message and how a corresponding handler removes the SOAP header from an incoming message. In addition, he presents the JAX-RPC programmatic configuration and deployment models as they relate to this topic.

In this article, I show you how you can extend a Web service, defined by WSDL, by adding SOAP headers. I show how SOAP handlers create and process SOAP message headers and how to configure handlers appropriately.

Basic JAX-RPC SOAP handlers

Let's start by examining a JAX-RPC invocation in Figure 1.


Figure 1. JAX-RPC/SOAP message flow
JAX-RPC/SOAP message flow

A single JAX-RPC invocation is represented on the wire as two SOAP messages: the request and the response. A WSDL document defines the service interface, and therefore the SOAP content, for both the request and the response.

When you deploy a JAX-RPC SOAP handler into the message flow, you gain access to both messages involved in the remote procedure call. JAX-RPC SOAP handlers are JAX-RPC handlers in that they support the RPC invocation model via the handleRequest(), handleResponse(), and handleFault() methods. They are SOAP handlers in that message content is represented to the handler as SOAP content in the form of a SAAJ object (see the Resources section for more on these objects).

J2EE Web services SOAP handlers

The J2EE Web services runtime, based on JAX-RPC, enforces restrictions on the changes that a SOAP handler may make to a SOAP message. Even here there are ample opportunities for mistakes. Responsibility remains with the handler for correct behavior.

Handlers manipulate primitive SOAP content directly, and are not aware of expected content as defined in WSDL. The handler developer is responsible for the correctness of the SOAP message, in contrast to the strongly-typed JAX-RPC service interfaces.

Extending services

SOAP headers provide the channel that allows out-of-band information to be exchanged between the client and service. This exchange of additional information provides an opportunity for extending the behavior represented by a service interface without changing the interface and, therefore, without changing WSDL. This is valid because SOAP headers need not be defined in WSDL. (For more on this, take a look at WS-I Basic Profile Version 1.0a; see Resources for a link.)

While a message can be extended with a SOAP header not defined by WSDL/Schema, the content of the SOAP header should still be well-defined. All producers and consumers of the header must agree on its content.

JAX-RPC SOAP handlers are the operational means for working with such out-of-band information. You can specifically work with such information by deploying a handler on the client and a corresponding handler on the service, as shown in Figure 2.


Figure 2. JAX-RPC/SOAP message flow with handlers
JAX-RPC/SOAP message flow with handlers

With respect to the SOAP header information, the four processing points represented by the two handlers are:

  • Client-side handleRequest(): Attach extended content into the outgoing request message.
  • Server-side handleRequest(): Extract extended content from the incoming request message.
  • Server-side handleResponse()/handleFault(): Attach extended content into the outgoing response message.
  • Client-side handleResponse()/handleFault(): Extract extended content from the incoming response message.

Note the point of information exchange within each handler from the request to the response message using the MessageContext, as discussed in the article "How to create a simple JAX-RPC handler" (see Resources for a link).

Let's explore these concepts in the context of a specific example.



Back to top


Example: Processing message content

For this simple example, I deploy collaborating handlers that sign outgoing messages by placing a signature in the header and verify that the signature on incoming messages is correct. The signature is a representation of the message body; if the body has been changed, the signature verification will fail. The signer's name must be in the handler's configuration; the client and service are not required to sign using the same name.

You need an algorithm for signing messages, which will be represented by the SignatureTool interface. Both the client and service handlers must sign outgoing and verify incoming messages, so an abstract class, SignHandler, will provide the common code. ClientSignHandler and ServiceSignHandler extend SignHandler and bring everything together.


Listing 1. SignatureTool.java
interface SignatureTool {
    /**
     * @return Result of signer signing content.
     */
    public SOAPElement getSignature(String signersName,
                                    SOAPElement content) throws SOAPException;


    /**
     * @return true if content was signed, unchanged, by signer of signature.
     * This is only true if the content is the same content signed originally
     * by a signer, resulting in signature.
     */
    public boolean isSignatureValid(SOAPElement signature,
                                    SOAPElement content) throws SOAPException;
}

Handlers can sign messages by calling SignatureTool.getSignature() with content extracted from an outgoing SOAP message. Likewise, verification of the signature is performed by calling isSignatureValid() with the signature and the content extracted from an incoming SOAP message.

Processing outgoing messages

You process an outgoing message by adding a signature to the message as a new header block. SignHandler.signOutgoing(), shown in Listing 2, demonstrates how to add a header block to an outgoing SOAP message. This method will:

  • Locate SOAPHeader in the envelope.
  • Add a new child SOAP header element.
  • Create and set content for the new header element.

Listing 2. SignHandler.signOutgoing()
abstract class SignHandler implements Handler
{
    . . .

    /**
     * Name of required property, in HandlerInfo handler configuration,
     * that specifies who signs outgoing messages.
     */
    public  static String SIGNERS_NAME_PROPERTY;
    
    private SignatureTool  signatureTool;
    private HandlerInfo    info;


    /**
     * Obtain signer's name from handler configuration (HandlerInfo),
     * and sign message.
     * @throws SignException if SIGNERS_NAME_PROPERTY is not available
     *                       in the handler config.
     */
    public void signOutgoing(SOAPMessageContext mc) throws SignException {
         // SIGNERS_NAME_PROPERTY is required to be on the configuration.
        Map config = info.getHandlerConfig();
        Object nameObj = config.get(SIGNERS_NAME_PROPERTY);
        String name = (String)nameObj;
        
        try {
            // Dig down into message, locate or create header block.
            SOAPMessage msg = mc.getMessage();
            SOAPPart part = msg.getSOAPPart();
            SOAPEnvelope envelope = part.getEnvelope();
            SOAPHeader header = envelope.getHeader();

            /**
             * Create new header element.
             * We don't specify a role on this header element,
             * meaning the target role is the "ultimate destination".
             */
            SOAPHeaderElement headerElement
                = (SOAPHeaderElement)header.addChildElement(SIGN_ELEMENT,
                                                            SIGN_PREFIX,
                                                            SIGN_NS_URI);

            // Locate portion of message content that is to be signed.
            SOAPElement content = getContent(part);

            /**
             * Create new element representing signature,
             * and add as child to new header element.
             */
            SOAPElement element = signatureTool.getSignature(name, content);
            headerElement.addChildElement(element);
        } catch (SOAPException e) {
            e.printStackTrace();
            throw new SignException("Unable to sign message", e);
        }
    }

    . . .
    // See resources for full sample code.
}

After processing, a SOAP envelope will look like the one in Listing 3.


Listing 3. Outbound SOAP message
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <!-- original header content -->

    <!-- New header element, inserted by handler -->
    <sign:sign xmlns:sign="uri://org.example.webservices.signature.Sign">
      <!-- output of SignatureTool.getSignature() -->
    </sign:sign>

  </soap:Header>
  <soap:Body>
    <!-- original body content -->
  </soap:Body>
</soap:Envelope>

Processing incoming messages

You process an incoming message to verify that the signature is valid. SignHandler.checkIncoming(), shown in Listing 4, demonstrates how to locate a header block, a non-obvious process. This method will:

  • Locate SOAPHeader in the envelope.
  • Determine the set of roles in which the current SOAP node is acting (more on this below).
  • Determine the set of headers that the current handler understands.
  • Process headers that this handler understands and that are bound to the set of roles in which the current SOAP node is acting.

Listing 4. SignHandler.checkIncoming()
abstract class SignHandler implements Handler
{
    . . .

    /**
     * Name of required property, in HandlerInfo handler configuration,
     * that specifies who signs outgoing messages.
     */
    public  static String SIGNERS_NAME_PROPERTY;
    
    private static QName   SIGN_HEADER;

    private SignatureTool  signatureTool;
    private HandlerInfo    info;


    /**
     * Look for signature on incoming message.
     * If signature not found, then continue message processing.
     * If signature is found, then verify that it is "correct".
     * If correct, then continue message processing.
     * If not correct, then throw SignException.
     */
    public void checkIncoming(SOAPMessageContext mc) throws SignException {
        try {
            SOAPMessage msg = mc.getMessage();
            SOAPPart part = msg.getSOAPPart();
            SOAPEnvelope envelope = part.getEnvelope();

            
            /**
             * Locate portion of message content that is to be signed.
             */
            SOAPElement content = getContent(part);


            /**
             * Dig down through SOAP headers looking for matches to
             * SIGN_HEADER, that are also targeted for this SOAP Node.
             */
            SOAPHeader header = envelope.getHeader();
            if (header != null) {
                /**
                 * The roles the node acts in are specified by the
                 * MessageContext.getRoles() method.
                 * If roles not found, default to "ultimate destination".
                 */
                String[] roles = mc.getRoles();
                if (roles == null  ||  roles.length == 0) {
                    roles = new String[] { "" };
                }

                for (int ridx = 0; ridx < roles.length; ridx++) {
                    String role = roles[ridx];

                    /**
                     * Examine headers bound to each role this node acts in.
                     * Headers are determined to be targeted for a SOAP Node by
                     * matching the node's roles with the header's actor role.
                     *
                     * So now go through list of headers associated with
                     * the role we are currently working on.
                     */
                    Iterator headerElementIter
                        = header.examineHeaderElements(role);
    
                    while (headerElementIter.hasNext()) {
                        SOAPHeaderElement headerElement
                            = (SOAPHeaderElement)headerElementIter.next();
    
                        // Is header recognized by this handler?
                        Name headerElementName
                            = headerElement.getElementName();

                        if (equals(headerElementName, SIGN_HEADER)) {
                            /**
                             * Look for SOAPElement(s) in header,
                             * ignoring mixed content.
                             */
                            Iterator headerIter
                                = headerElement.getChildElements();
    
                            while (headerIter.hasNext()) {
                                Object elementObj = headerIter.next();
    
                                if (elementObj instanceof SOAPElement) {
                                    SOAPElement element
                                        = (SOAPElement)elementObj;

                                    signatureTool.isSignatureValid(element,
                                                                   content);
                                }
                            }
                        }
                    }
                }
            }
        } catch (SOAPException e) {
            e.printStackTrace();
            throw new SignException("Unable to verify signature", e);
        }
    }

    . . .
    // See resources for full sample code.
}

More on roles
The roles in which a SOAP node acts are expected to be available from the MessageContext. The code in Listing 4 demonstrates that WebSphere Application Server maps the empty string to the ultimate destination (the default actor). The proper value for the ultimate destination is not clearly specified by the SOAP 1.1 specification, so this may vary from implementation to implementation. A more detailed discussion will be deferred to a future tip.

The handlers

The SignHandler class is oriented toward processing incoming and outgoing messages. The handler interface is oriented toward processing request and response messages. The two come together in the handler implementations.

ClientSignHandler, shown in Listing 5, is very simple. It directs the request message context for processing as an outgoing message and the response/fault message context for processing as an incoming message. This complements the ServiceSignHandler.


Listing 5. ClientSignHandler
public class ClientSignHandler extends SignHandler
{
    public boolean handleRequest(MessageContext mc) {
        signOutgoing((SOAPMessageContext)mc);
        return true;
    }

    public boolean handleResponse(MessageContext mc) {
        checkIncoming((SOAPMessageContext)mc);
        return true;
    }

    public boolean handleFault(MessageContext mc) {
        checkIncoming((SOAPMessageContext)mc);
        return true;
    }
}

ServiceSignHandler, shown in Listing 6, is just as simple. It directs the request message context for processing as an incoming message and response/fault message context for processing as an outgoing message. This complements the ClientSignHandler.


Listing 6. ServiceSignHandler
public class ServiceSignHandler extends SignHandler
{
    public boolean handleRequest(MessageContext mc) {
        checkIncoming((SOAPMessageContext)mc);
        return true;
    }

    public boolean handleResponse(MessageContext mc) {
        signOutgoing((SOAPMessageContext)mc);
        return true;
    }

    public boolean handleFault(MessageContext mc) {
        signOutgoing((SOAPMessageContext)mc);
        return true;
    }
}



Back to top


Configuring JAX-RPC SOAP handlers

To complete this example, I configure and deploy the handlers. The handlers look for a configuration property specifying the signer's name. More importantly, the runtime expects all header elements processed by handlers to be specified in the deployment information.

For the client, I will deploy the handler using the JAX-RPC programmatic interface. For the service, I will deploy the handler using a J2EE for Web Services deployment descriptor.

JAX-RPC 1.0 programmatic configuration: HandlerInfo()

HandlerInfo(), shown in Listing 7, defines the handler, its configuration, and the header elements that the handler is expected to process. In this example, the header elements are exposed as the static property ClientSignHandler.HEADERS.


Listing 7. Setting up HandlerInfo()
        Map hConfig = new HashMap();
        hConfig.put(ClientSignHandler.SIGNERS_NAME_PROPERTY, "Linus");

        HandlerInfo hInfo = new HandlerInfo(ClientSignHandler.class,
                                            hConfig,
                                            ClientSignHandler.HEADERS);

To register the handler, the HandlerInfo() must be added (appended) to the end of the handler chain. It's a good practice to make no assumption about handlers that may or may not be on the handler chain.


Listing 8. Registering the handler
        // Obtain service, for JAX-RPC 1.0 this may be implementation specific.
        StockQuoteService service = ...;

        // Must match WSDL's port QName
        QName portQName = new QName("http://stock.webservices.example.org",
                                    "StockQuote");

        service.getHandlerRegistry().getHandlerChain(portQName).add(hInfo);

Web Services for J2EE 1.3 deployment model: webservices.xml

In a J2EE managed environment, use of the programmatic interface will result in an exception. The J2EE deployment descriptor for Web services, webservices.xml, must be used to specify the handlers deployed on a port.

The Web services deployment descriptor mirrors the information set programmatically using HandlerInfo(). It specifies name/value pair configuration properties (init-param), the handler class name (handler-class), and the headers that the handler understands (soap-header). You can see all of this in action in Listing 9.


Listing 9. webservices.xml
  . . .
    <port-component>
      <port-component-name>StockQuote</port-component-name>

      <wsdl-port>
        <namespaceURI>http://stock.webservices.example.org</namespaceURI>
        <localpart>StockQuote</localpart>
      </wsdl-port>

      . . .

      <handler>
        <handler-name>Signature Handler</handler-name>
        <handler-class>org.example.webservices.signature.
        ServiceSignHandler</handler-class>

        <init-param>
          <param-name>org.example.webservices.signature.SignersName</param-name>
          <param-value>Snoopy</param-value>
        </init-param>

        <soap-header>
          <namespaceURI>uri://
          org.example.webservices.signature.Sign</namespaceURI>
          <localpart>sign</localpart>
        </soap-header>
      </handler>
    </port-component>
  . . .



Back to top


Some best practices for implementing handlers

I've shown you a few best-practices related to designing and implementing handlers:

  • Leverage symmetry of message flow, where appropriate. Client request processing mirrored by service response/fault processing and service request processing mirrored by client response/fault processing, is a recurring pattern in handler development. Provide a common implementation based on the outbound/inbound pattern to leverage common code.

  • Separate processing of SOAPHeaderElement from processing of header content: In this sample, I separated processing of the SOAPHeaderElement from processing of the header content. The handler locates and processes the SOAPHeaderElement. The structure and processing of the header element content has been delegated to SignatureTool by abstracting the content to a single SOAPElement.

  • Understood headers should be defined by HandlerInfo or a deployment descriptor. This is more of a rule than a best practice. All headers to be processed by a handler should be defined to the runtime via HandlerInfo or the appropriate deployment descriptor. JAX-RPC does not provide a similar point of configuration for headers that are created by a handler. This topic will be revisited in a future tip.


Back to top


Summary

Existing SOAP-based Web services can be extended by client/service handlers that collaborate to process outbound and inbound messages. Processing outbound messages is fairly straightforward, while processing inbound messages is nontrivial. In this tip, you saw one road map for processing inbound messages, and learned that the runtime must always be configured with the header elements that are to be processed by each handler.




Back to top


Download

NameSizeDownload method
ws-tip-extend.zip41.0 KBHTTP
Information about download methods


Resources



About the author

Richard A. Sitze is a member of the IBM WebSphere Web services development team. He has been involved with the Apache Axis SOAP engine, Jakarta Commons Logging, and Jakarta Commons Discovery open source projects. His previous work with IBM includes CORBA ORB interoperability and Internet banking services. His work before IBM included business systems, real-time control systems, firmware, networking communication protocols, threaded kernels, and multi-processor UNIX kernel development. Contact Richard at rsitze@us.ibm.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top