4. Shared components

In this chapter, we will explore the components which are shared between client- and server-side Spring-WS development. These interfaces and classes represent the building blocks of Spring-WS, so it is important to understand what they do, even if you do not use them directly.

4.1 Web service messages

4.1.1 WebServiceMessage

One of the core interfaces of Spring Web Services is the WebServiceMessage. This interface represents a protocol-agnostic XML message. The interface contains methods that provide access to the payload of the message, in the form of a javax.xml.transform.Source or a javax.xml.transform.Result. Source and Result are tagging interfaces that represent an abstraction over XML input and output. Concrete implementations wrap various XML representations, as indicated in the following table.

Source/Result implementationWraps XML representation
javax.xml.transform.dom.DOMSourceorg.w3c.dom.Node
javax.xml.transform.dom.DOMResultorg.w3c.dom.Node
javax.xml.transform.sax.SAXSourceorg.xml.sax.InputSource and org.xml.sax.XMLReader
javax.xml.transform.sax.SAXResultorg.xml.sax.ContentHandler
javax.xml.transform.stream.StreamSource java.io.File, java.io.InputStream, or java.io.Reader
javax.xml.transform.stream.StreamResult java.io.File, java.io.OutputStream, or java.io.Writer

In addition to reading from and writing to the payload, a Web service message can write itself to an output stream.

4.1.2 SoapMessage

The SoapMessage is a subclass of WebServiceMessage. It contains SOAP-specific methods, such as getting SOAP Headers, SOAP Faults, etc. Generally, your code should not be dependent on SoapMessage, because the content of the SOAP Body (the payload of the message) can be obtained via getPayloadSource() and getPayloadResult() in the WebServiceMessage. Only when it is necessary to perform SOAP-specific actions, such as adding a header, getting an attachment, etc., should you need to cast WebServiceMessage to SoapMessage.

4.1.3 Message Factories

Concrete message implementations are created by a WebServiceMessageFactory. This factory can create an empty message, or read a message based on an input stream. There are two concrete implementations of WebServiceMessageFactory; one is based on SAAJ, the SOAP with Attachments API for Java, the other based on Axis 2's AXIOM, the AXis Object Model.

SaajSoapMessageFactory

The SaajSoapMessageFactory uses the SOAP with Attachments API for Java to create SoapMessage implementations. SAAJ is part of J2EE 1.4, so it should be supported under most modern application servers. Here is an overview of the SAAJ versions supplied by common application servers:

Application ServerSAAJ Version
BEA WebLogic 81.1
BEA WebLogic 91.1/1.2[1]
IBM WebSphere 61.2
SUN Glassfish 11.3

[1] Weblogic 9 has a known bug in the SAAJ 1.2 implementation: it implement all the 1.2 interfaces, but throws a UnsupportedOperationException when called. Spring Web Services has a workaround: it uses SAAJ 1.1 when operating on WebLogic 9.

Additionally, Java SE 6 includes SAAJ 1.3. You wire up a SaajSoapMessageFactory like so:

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />

[Note]Note

SAAJ is based on DOM, the Document Object Model. This means that all SOAP messages are stored in memory. For larger SOAP messages, this may not be very performant. In that case, the AxiomSoapMessageFactory might be more applicable.

AxiomSoapMessageFactory

The AxiomSoapMessageFactory uses the AXis 2 Object Model to create SoapMessage implementations. AXIOM is based on StAX, the Streaming API for XML. StAX provides a pull-based mechanism for reading XML messages, which can be more efficient for larger messages.

To increase reading performance on the AxiomSoapMessageFactory, you can set the payloadCaching property to false (default is true). This will read the contents of the SOAP body directly from the socket stream. When this setting is enabled, the payload can only be read once. This means that you have to make sure that any pre-processing (logging etc.) of the message does not consume it.

You use the AxiomSoapMessageFactory as follows:

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

In addition to payload caching, AXIOM also supports full streaming messages, as defined in the StreamingWebServiceMessage. This means that the payload can be directly set on the response message, rather than being written to a DOM tree or buffer.

Full streaming for AXIOM is used when a handler method returns a JAXB2-supported object. It will automatically set this marshalled object into the response message, and write it out to the outgoing socket stream when the response is going out.

For more information about full streaming, refer to the class-level Javadoc for StreamingWebServiceMessage and StreamingPayload.

SOAP 1.1 or 1.2

Both the SaajSoapMessageFactory and the AxiomSoapMessageFactory have a soapVersion property, where you can inject a SoapVersion constant. By default, the version is 1.1, but you can set it to 1.2 like so:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd">

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

In the example above, we define a SaajSoapMessageFactory that only accepts SOAP 1.2 messages.

[Caution]Caution

Even though both versions of SOAP are quite similar in format, the 1.2 version is not backwards compatible with 1.1 because it uses a different XML namespace. Other major differences between SOAP 1.1 and 1.2 include the different structure of a Fault, and the fact that SOAPAction HTTP headers are effectively deprecated, thought they still work.

One important thing to note with SOAP version numbers, or WS-* specification version numbers in general, is that the latest version of a specification is generally not the most popular version. For SOAP, this means that currently, the best version to use is 1.1. Version 1.2 might become more popular in the future, but currently 1.1 is the safest bet.

4.1.4 MessageContext

Typically, messages come in pairs: a request and a response. A request is created on the client-side, which is sent over some transport to the server-side, where a response is generated. This response gets sent back to the client, where it is read.

In Spring Web Services, such a conversation is contained in a MessageContext, which has properties to get request and response messages. On the client-side, the message context is created by the WebServiceTemplate. On the server-side, the message context is read from the transport-specific input stream. For example, in HTTP, it is read from the HttpServletRequest and the response is written back to the HttpServletResponse.

4.2 TransportContext

One of the key properties of the SOAP protocol is that it tries to be transport-agnostic. This is why, for instance, Spring-WS does not support mapping messages to endpoints by HTTP request URL, but rather by mesage content.

However, sometimes it is necessary to get access to the underlying transport, either on the client or server side. For this, Spring Web Services has the TransportContext. The transport context allows access to the underlying WebServiceConnection, which typically is a HttpServletConnection on the server side; or a HttpUrlConnection or CommonsHttpConnection on the client side. For example, you can obtain the IP address of the current request in a server-side endpoint or interceptor like so:

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.3 Handling XML With XPath

One of the best ways to handle XML is to use XPath. Quoting [effective-xml], item 35:

 

XPath is a fourth generation declarative language that allows you to specify which nodes you want to process without specifying exactly how the processor is supposed to navigate to those nodes. XPath's data model is very well designed to support exactly what almost all developers want from XML. For instance, it merges all adjacent text including that in CDATA sections, allows values to be calculated that skip over comments and processing instructions` and include text from child and descendant elements, and requires all external entity references to be resolved. In practice, XPath expressions tend to be much more robust against unexpected but perhaps insignificant changes in the input document.

 
 --Elliotte Rusty Harold

Spring Web Services has two ways to use XPath within your application: the faster XPathExpression or the more flexible XPathTemplate.

4.3.1 XPathExpression

The XPathExpression is an abstraction over a compiled XPath expression, such as the Java 5 javax.xml.xpath.XPathExpression, or the Jaxen XPath class. To construct an expression in an application context, there is the XPathExpressionFactoryBean. Here is an example which uses this factory bean:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

The expression above does not use namespaces, but we could set those using the namespaces property of the factory bean. The expression can be used in the code as follows:

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

For a more flexible approach, you can use a NodeMapper, which is similar to the RowMapper in Spring's JDBC support. The following example shows how we can use it:

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      // do something with list of Contact objects
   }
}

Similar to mapping rows in Spring JDBC's RowMapper, each result node is mapped using an anonymous inner class. In this case, we create a Contact object, which we use later on.

4.3.2 XPathTemplate

The XPathExpression only allows you to evaluate a single, pre-compiled expression. A more flexible, though slower, alternative is the XpathTemplate. This class follows the common template pattern used throughout Spring (JdbcTemplate, JmsTemplate, etc.). Here is an example:

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.4 Message Logging and Tracing

When developing or debugging a Web service, it can be quite useful to look at the content of a (SOAP) message when it arrives, or just before it is sent. Spring Web Services offer this functionality, via the standard Commons Logging interface.

[Caution]Caution

Make sure to use Commons Logging version 1.1 or higher. Earlier versions have class loading issues, and do not integrate with the Log4J TRACE level.

To log all server-side messages, simply set the org.springframework.ws.server.MessageTracing logger to level DEBUG or TRACE. On the debug level, only the payload root element is logged; on the TRACE level, the entire message content. If you only want to log sent messages, use the org.springframework.ws.server.MessageTracing.sent logger; or org.springframework.ws.server.MessageTracing.received to log received messages.

On the client-side, similar loggers exist: org.springframework.ws.client.MessageTracing.sent and org.springframework.ws.client.MessageTracing.received.

Here is an example log4j.properties configuration, logging the full content of sent messages on the client side, and only the payload root element for client-side received messages. On the server-side, the payload root is logged for both sent and received messages:

log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG

log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n

With this configuration, a typical output will be:

TRACE [client.MessageTracing.sent] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ...
DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ...
DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...