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.
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 implementation | Wraps XML representation |
---|---|
javax.xml.transform.dom.DOMSource | org.w3c.dom.Node |
javax.xml.transform.dom.DOMResult | org.w3c.dom.Node |
javax.xml.transform.sax.SAXSource | org.xml.sax.InputSource and
org.xml.sax.XMLReader
|
javax.xml.transform.sax.SAXResult | org.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.
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
.
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.
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 Server | SAAJ Version |
---|---|
BEA WebLogic 8 | 1.1 |
BEA WebLogic 9 | 1.1/1.2[a] |
IBM WebSphere 6 | 1.2 |
SUN Glassfish 1 | 1.3 |
[a]
Weblogic 9 has a known bug in the SAAJ 1.2
implementation: it implement all the 1.2 interfaces, but throws a
|
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" />
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.
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
.
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.
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.
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
.
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();
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
.
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.
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
}
}
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.
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] ...