Spring-WS's server-side support is designed around a
MessageDispatcher
that dispatches incoming
messages to endpoints, with configurable endpoint mappings, response
generation, and endpoint interception.
The simplest endpoint is a PayloadEndpoint
, which just offers the
Source invoke(Source request)
method. You are of course free to
implement this interface directly, but you will probably prefer to extend one of
the included abstract implementations such as
AbstractDomPayloadEndpoint
, AbstractSaxPayloadEndpoint
, and
AbstractMarshallingPayloadEndpoint
.
Alternatively, there is a endpoint development that uses Java 5 annotations, such as
@Endpoint
for marking a POJO as endpoint, and marking a method with
@PayloadRoot
or @SoapAction
.
Spring-WS's XML handling is extremely flexible. An endpoint can choose from a large amount of XML handling libraries supported by Spring-WS, including the DOM family (W3C DOM, JDOM, dom4j, and XOM), SAX or StAX for faster performance, XPath to extract information from the message, or even marshalling techniques (JAXB, Castor, XMLBeans, JiBX, or XStream) to convert the XML to objects and vice-versa.
The server-side of Spring-WS is designed around a central class that dispatches incoming XML messages to
endpoints. Spring-WS's MessageDispatcher
is extremely flexible, allowing you to
use any sort of class as an endpoint, as long as it can be configured in the Spring IoC container.
In a way, the message dispatcher resembles Spring's DispatcherServlet
, the
“Front Controller” used in Spring Web MVC.
The processing and dispatching flow of the MessageDispatcher
is illustrated in the
following sequence diagram.
When a MessageDispatcher
is set up for use and a request comes in for that
specific dispatcher, said MessageDispatcher
starts processing the request. The
list below describes the complete process a request goes through when handled by a
MessageDispatcher
:
An appropriate endpoint is searched for using the configured EndpointMapping(s)
.
If an endpoint is found, the invocation chain associated with the endpoint (preprocessors,
postprocessors, and endpoints) will be executed in order to create a response.
An appropriate adapter is searched for the endpoint. The MessageDispatcher
delegates to this adapter to invoke the endpoint.
If a response is returned, it is sent on its way. If no response is returned (which could be due to a pre- or post-processor intercepting the request, for example, for security reasons), no response is sent.
Exceptions that are thrown during handling of the request get picked up by any of the endpoint exception resolvers that are declared in the application context. Using these exception resolvers allows you to define custom behaviors (such as returning a SOAP Fault) in case such exceptions get thrown.
The MessageDispatcher
has several properties, for setting endpoint adapters,
mappings,
exception resolvers.
However, setting these properties is not required, since the dispatcher will automatically detect all of
these types that are registered in the application context. Only when detection needs to be overriden,
should these properties be set.
The message dispatcher operates on a message context, and not
transport-specific input stream and output stream. As a result, transport specific requests need to read
into a MessageContext
. For HTTP, this is done with a
WebServiceMessageReceiverHandlerAdapter
, which is a Spring Web
HandlerInterceptor
, so that the MessageDispatcher
can be wired in a standard DispatcherServlet
. There is a more convenient way to do
this, however, which is shown in the next section.
The MessageDispatcherServlet
is a standard Servlet
which
conveniently extends from the standard Spring Web DispatcherServlet
, and wraps
a MessageDispatcher
. As such, it combines the attributes of these into one:
as a MessageDispatcher
, it follows the same request handling flow as described
in the previous section.
As a servlet, the
MessageDispatcherServlet
is configured in the web.xml
of
your web application. Requests that you want the MessageDispatcherServlet
to
handle will have to be mapped using a URL mapping in the same web.xml
file. This is
standard Java EE servlet configuration; an example of such a
MessageDispatcherServlet
declaration and mapping can be found below.
<web-app> <servlet> <servlet-name>spring-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
In the example above, all requests will be handled by the 'spring-ws'
MessageDispatcherServlet
. This is only the first step in setting up Spring Web
Services, because the various component beans used by the Spring-WS framework also need to be
configured; this configuration consists of standard Spring XML <bean/>
definitions. Because the MessageDispatcherServlet
is a standard Spring
DispatcherServlet
, it will look for a file named
[servlet-name]-servlet.xml
in the WEB-INF
directory
of your web application and create the beans defined there in a Spring container. In the example above,
that means that it looks for '/WEB-INF/spring-ws-servlet.xml
'. This file will
contain all of the SWS-specific beans such as endpoints, marshallers and suchlike.
The MessageDispatcherServlet
will automatically detect any
WsdlDefinition
beans defined in it's Spring container. All such
WsdlDefinition
beans that are detected will also be exposed via
a WsdlDefinitionHandlerAdapter
; this is a very convenient way to expose your
WSDL to clients simply by just defining some beans.
By way of an example, consider the following bean definition, defined in the Spring-WS framework's
configuration file ('/WEB-INF/[servlet-name]-servlet.xml
'). Take notice of the
value of the bean's 'id
' attribute, because this will be used when exposing
the WSDL.
<bean id="orders" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition"> <constructor-arg value="/WEB-INF/wsdl/Orders.wsdl"/> </bean>
The WSDL defined in the 'Orders.wsdl
' file can then be accessed via
GET
requests to a URL of the following form (substitute the host, port and
servlet context path as appropriate).
http://localhost:8080/spring-ws/orders.wsdl
Another cool feature of the MessageDispatcherServlet
(or more correctly the
WsdlDefinitionHandlerAdapter
) is that it is able to
transform the value of the 'location
' of all the WSDL that it exposes to reflect
the URL of the incoming request.
Please note that this 'location
' transformation feature is
off by default.To switch this feature on, you just need to specify an
initialization parameter to the MessageDispatcherServlet
, like so:
<web-app> <servlet> <servlet-name>spring-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> <init-param> <param-name>transformWsdlLocations</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Consult the class-level Javadoc on the WsdlDefinitionHandlerAdapter
class
which explains the whole transformation process in more detail.
As indicated above, a static WSDL file can be exposed by using the
SimpleWsdl11Definition
. Simply wire it up, and give it a
Resource
for the wsdl property, or use the
contructor, as shown in the example above.
As shown in Section 3.7, “Publishing the WSDL”, Spring Web Services can generate a WSDL file from a XSD schema, using conventions. The next application context snippet shows how to create such a dynamic WSDL file:
<bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition"> <property name="builder"> <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder"> <property name="schema" value="/WEB-INF/xsd/Orders.xsd"/> <property name="portTypeName" value="Orders"/> <property name="locationUri" value="http://localhost:8080/ordersService/"/> </bean> </property> </bean>
The DynamicWsdl11Definition
uses a
Wsdl11DefinitionBuilder
implementation
to generate a WSDL the first time it is requested.
Typically, we use a XsdBasedSoap11Wsdl4jDefinitionBuilder
, which builds
a WSDL from a XSD schema. This builder iterates over all element
elements
found in the schema, and creates a message
for elements that end with the
defined request or response suffix. The default request suffix is Request
;
the default response suffix is Response
, though these can be changed by
setting the requestSuffix and responseSuffix
properties, respectively.
Next, the builder combines the request and response messages into a WSDL
operation
s, and builds a portType
based on the operations.
For instance, if our Orders.xsd
schema defines the
GetOrdersRequest
and GetOrdersResponse
elements, the
XsdBasedSoap11Wsdl4jDefinitionBuilder
will create a
GetOrdersRequest
and GetOrdersResponse
message, and a
GetOrders
operation, which is put in a Orders
port type.
As an alternative to the MessageDispatcherServlet
, you can wire up a
MessageDispatcher
in a standard, Spring-Web MVC
DispatcherServlet
.
By default, the DispatcherServlet
can only delegate to
Controllers
, but we can instruct it to delegate to a
MessageDispatcher
by adding a
WebServiceMessageReceiverHandlerAdapter
to the servlet's web application
context:
<beans> <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="defaultHandler" ref="messageDispatcher"/> </bean <bean id="messageDispatcher" class="org.springframework.ws.server.MessageDispatcher"/> ... <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
Note that by explicitely adding the WebServiceMessageReceiverHandlerAdapter
,
the dispatcher servlet does not load the default adapters, and is unable to handle standard Spring-MVC
Controllers
. Therefore, we add the
SimpleControllerHandlerAdapter
at the end.
Endpoints are the central concept in Spring-WS's server-side support. Endpoints provide access to the application behavior which is typically defined by a business service interface. An endpoint interprets the XML request message and uses that input to invoke a method on the business service (typically). The result of that service invocation is represented as a response message. Spring-WS has a wide variety of endpoints, using various ways to handle the XML message, and to create a response.
The basis for most endpoints in Spring Web Services is the
org.springframework.ws.server.endpoint.PayloadEndpoint
interface, the source
code of which is listed below.
public interface PayloadEndpoint {
/**
* Invokes an operation.
*/
Source invoke(Source request) throws Exception;
}
As you can see, the PayloadEndpoint
interface defines a single method that
is invoked with the XML payload of a request (typically the contents of the SOAP Body, see
Section 4.1.2, “SoapMessage
”). The returned Source
, if any, is stored in the
response XML message. While the PayloadEndpoint
interface is quite abstract,
Spring-WS offers a lot of endpoint implementations out of the box that already contain a lot of the
functionality you might need. The PayloadEndpoint
interface just defines the
most basic responsibility required of every endpoint; namely handling a request and returning a response.
Alternatively, there is the MessageEndpoint
, which operates on a
whole MessageContext
rather than just
the payload. Typically, your code should not be dependent on messages, because the payload should
contain the information of interest. Only when it is necessary to perform actions on the message as a whole,
such as adding a SOAP header, get an attachment, and so forth, should you need to implement
MessageEndpoint
, though these actions are usually performed in an
endpoint interceptor.
One of the most basic ways to handle the incoming XML payload is by using a DOM (Document Object Model)
API. By extending from AbstractDomPayloadEndpoint
, you can use the
org.w3c.dom.Element and related classes to handle the request and create the
response. When using the AbstractDomPayloadEndpoint
as the baseclass for your
endpoints you only have to override the invokeInternal(Element, Document)
method, implement your logic, and return an Element
if a response is
necessary. Here is a short example consisting of a class and a declaration in the application context.
package samples; public class SampleEndpoint extends AbstractDomPayloadEndpoint { private String responseText; public SampleEndpoint(String responseText) { this.responseText = responseText; } protected Element invokeInternal( Element requestElement, Document document) throws Exception { String requestText = requestElement.getTextContent(); System.out.println("Request text: " + requestText); Element responseElement = document.createElementNS("http://samples", "response"); responseElement.setTextContent(responseText); return responseElement; } }
<bean id="sampleEndpoint" class="samples.SampleEndpoint"> <constructor-arg value="Hello World!"/> </bean>
The above class and the declaration in the application context are all you need besides setting up an endpoint mapping (see the section entitled Section 5.4, “Endpoint mappings”) to get this very simple endpoint working. The SOAP message handled by this endpoint will look something like:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<request xmlns="http://samples">
Hello
</request>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Though it could also handle the following Plain Old XML (POX) message, since we are only working on the payload of the message, and do not care whether it is SOAP or POX.
<request xmlns="http://samples"> Hello </request>
The SOAP response looks like:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<response xmlns="http://samples">
Hello World!
</response>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Besides the AbstractDomPayloadEndpoint
, which uses W3C DOM, there are other
base classes which use alternative DOM APIs. Spring Web Services supports most DOM APIs, so that you
can use the one you are familiar with. For instance, the
AbstractJDomPayloadEndpoint
allows you to use JDOM, and the
AbstractXomPayloadEndpoint
uses XOM to handle the XML. All of these endpoints
have an invokeInternal
method similar to above.
Also, consider using Spring-WS's XPath support to extract the information you need out of the payload.
(See the section entitled Section 4.3, “Handling XML With XPath” for details.)
Rather than handling XML directly using DOM, you can use marshalling to convert the payload of the XML
message into a Java Object. Spring Web Services offers the
AbstractMarshallingPayloadEndpoint
for this purpose, which is built on the
marshalling abstraction described in Chapter 8, Marshalling XML using O/X Mappers. The
AbstractMarshallingPayloadEndpoint
has two properties:
marshaller and unmarshaller, in which you can inject in the
constructor or by setters.
When extending from AbstractMarshallingPayloadEndpoint
, you have to override
the invokeInternal(Object)
method, where the passed
Object
represents the unmarshalled request payload, and return an
Object
that will be marshalled into the response payload. Here is an
example:
package samples; import org.springframework.oxm.Marshaller; import org.springframework.oxm.Unmarshaller; public class MarshallingOrderEndpoint extends AbstractMarshallingPayloadEndpoint{ private final OrderService orderService; public SampleMarshallingEndpoint(OrderService orderService, Marshaller marshaller) { super(marshaller); this.orderService = orderService; } protected Object invokeInternal(Object request) throws Exception { OrderRequest orderRequest = (OrderRequest) request; Order order = orderService.getOrder(orderRequest.getId()); return order; } }
<beans>
<bean id="orderEndpoint" class="samples.MarshallingOrderEndpoint">
<constructor-arg ref="orderService"/>
<constructor-arg ref="marshaller"/>
</bean>
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>samples.OrderRequest</value>
<value>samples.Order</value>
</list>
</property>
</bean>
<bean id="orderService" class="samples.DefaultOrderService"/>
<!-- Other beans, such as the endpoint mapping -->
</beans>
In this sample, we configure a Jaxb2Marshaller for the
OrderRequest
and Order
classes, and inject that
marshaller together with the
DefaultOrderService
into our endpoint. This business service is not shown, but
it is a normal transactional service, probably using DAOs to obtain data from a database.
In the invokeInternal
method, we cast the request object to an
OrderRequest
object, which is the JAXB object representing the payload of the
request. Using the identifier of that request, we obtain an order from our business service and return
it. The returned object is marshalled into XML, and used as the payload of the response message.
The SOAP request handled by this endpoint will look like:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <orderRequest xmlns="http://samples" id="42"/> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
The resulting response will be something like:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <order xmlns="http://samples" id="42"> <item id="100"> <quantity>1</quantity> <price>20.0</price> </item> <item id="101"> <quantity>1</quantity> <price>10.0</price> </item> </order> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Instead of JAXB 2, we could have used any of the other marshallers described in Chapter 8, Marshalling XML using O/X Mappers.
The only thing that would change in the above example is the configuration of the
marshaller
bean.
The previous two programming models were based on inheritance, and handled individual XML messages. Spring Web Services offer another endpoint with which you can aggregate multiple handling into one controller, thus grouping functionality together. This model is based on annotations, so you can use it only with Java 5 and higher. Here is an example that uses the same marshalled objects as above:
package samples; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; @Endpoint public class AnnotationOrderEndpoint { private final OrderService orderService; public AnnotationOrderEndpoint(OrderService orderService) { this.orderService = orderService; } @PayloadRoot(localPart = "orderRequest", namespace = "http://samples") public Order getOrder(OrderRequest orderRequest) { return orderService.getOrder(orderRequest.getId()); } @PayloadRoot(localPart = "order", namespace = "http://samples") public void order(Order order) { orderService.createOrder(order); } }
By annotating the class with @Endpoint
, you mark it as a Spring-WS
endpoint. Because the endpoint class can have multiple request handling methods, we need to instruct
Spring-WS which method to invoke for which request. This is done using the
@PayloadRoot
annotation: the getOrder
method
will be invoked for requests with a orderRequest
local name and a
http://samples
namespace URI; the order
method for requests with
a order
local name. For more information about these annotations, refer to
Section 5.4.3, “MethodEndpointMapping
”.
We also need to configure Spring-WS to support the JAXB objects OrderRequest
and
Order
by defining a Jaxb2Marshaller
:
<beans> <bean id="orderEndpoint" class="samples.AnnotationOrderEndpoint"> <constructor-arg ref="orderService"/> </bean> <bean id="orderService" class="samples.DefaultOrderService"/> <bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter"> <constructor-arg ref="marshaller"/> </bean> <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="classesToBeBound"> <list> <value>samples.OrderRequest</value> <value>samples.Order</value> </list> </property> </bean> <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/> </beans>
The GenericMarshallingMethodEndpointAdapter
converts the incoming
XML messages to marshalled objects used as parameters and return value; the
PayloadRootAnnotationMethodEndpointMapping
is the mapping that detects and
handles the @PayloadRoot
annotations.
As an alternative to using marshalling, we could have used XPath to
extract the information out of the incoming XML request. Spring-WS offers another annotation for
this purpose: @XPathParam
. You simply annotate one or more method
parameter with this annotation (each), and each such annotated parameter will be bound to the
evaluation of that annotation. Here is an example:
package samples;
import javax.xml.transform.Source;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;
@Endpoint
public class AnnotationOrderEndpoint {
private final OrderService orderService;
public AnnotationOrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
public Source getOrder(@XPathParam("/s:orderRequest/@id") double orderId) {
Order order = orderService.getOrder((int) orderId);
// create Source
from order and return it
}
}
Since we use the prefix 's
' in our XPath expression, we must bind it to the
http://samples
namespace:
<beans>
<bean id="orderEndpoint" class="samples.AnnotationOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
<bean id="orderService" class="samples.DefaultOrderService"/>
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>
<bean class="org.springframework.ws.server.endpoint.adapter.XPathParamAnnotationMethodEndpointAdapter">
<property name="namespaces">
<props>
<prop key="s">http://samples</prop>
</props>
</property>
</bean>
</beans>
Using the @XPathParam
, you can bind to all the data types supported by
XPath:
boolean or Boolean
double or Double
String
Node
NodeList
The endpoint mapping is responsible for mapping incoming messages to appropriate endpoints. There are some
endpoint mappings you can use out of the box, for example, the
PayloadRootQNameEndpointMapping
or the
SoapActionEndpointMapping
, but let's first examine the general concept of an
EndpointMapping
.
An EndpointMapping
delivers a EndpointInvocationChain
,
which contains the endpoint that matches the incoming request, and may also contain a list of endpoint
interceptors that will be applied to the request and response. When a request comes in, the
MessageDispatcher
will hand it over to the endpoint mapping to let it inspect the
request and come up with an appropriate EndpointInvocationChain
. Then
the MessageDispatcher
will invoke the endpoint and any interceptors in the chain.
The concept of configurable endpoint mappings that can optionally contain interceptors (which can manipulate
the request or the response, or both) is extremely powerful. A lot of supporting functionality can be built
into custom EndpointMapping
s. For example, there could be a custom endpoint
mapping that chooses an endpoint not only based on the contents of a message, but also on a specific SOAP
header (or indeed multiple SOAP headers).
Most endpoint mappings inherit from the AbstractEndpointMapping
, which offers an
'interceptors' property, which is the list of interceptors to use.
EndpointInterceptors
are discussed in
Section 5.4.4, “Intercepting requests - the EndpointInterceptor
interface”. Additionally, there is the
'defaultEndpoint', which is the default endpoint to use, when this endpoint mapping does
not result in a matching endpoint.
The PayloadRootQNameEndpointMapping
will use the qualified name of the root
element of the request payload to determine the endpoint that handles it. A qualified name consists of
a namespace URI and a local part, the combination of which
should be unique within the mapping. Here is an example:
<beans>
<!-- no 'id'
required, EndpointMapping
beans are automatically detected by the MessageDispatcher
-->
<bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
<prop key="{http://samples}orderRequest">getOrderEndpoint</prop>
<prop key="{http://samples}order">createOrderEndpoint</prop>
</props>
</property>
</bean>
<bean id="getOrderEndpoint" class="samples.GetOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
<bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
<beans>
The qualified name is expressed as {
+ namespace URI + }
+
local part. Thus, the endpoint mapping above routes requests for which have a payload root element with
namespace http://samples
and local part orderRequest
to the
'getOrderEndpoint'
. Requests with a local part order
will
be routed to the 'createOrderEndpoint'
.
Rather than base the routing on the contents of the message with the
PayloadRootQNameEndpointMapping
, you can use the SOAPAction
HTTP header to route messages. Every client sends this header when making a SOAP request, and the
header value used for a request is defined in the WSDL. By making the SOAPAction
unique per operation, you can use it as a discriminator. Here is an example:
<beans> <bean id="endpointMapping" class="org.springframework.ws.soap.server.endpoint.mapping.SoapActionEndpointMapping"> <property name="mappings"> <props> <prop key="http://samples/RequestOrder">getOrderEndpoint</prop> <prop key="http://samples/CreateOrder">createOrderEndpoint</prop> </props> </property> </bean> <bean id="getOrderEndpoint" class="samples.GetOrderEndpoint"> <constructor-arg ref="orderService"/> </bean> <bean id="createOrderEndpoint" class="samples.CreateOrderEndpoint"> <constructor-arg ref="orderService"/> </bean> </beans>
The mapping above routes requests which have a SOAPAction
of
http://samples/RequestOrder
to the 'getOrderEndpoint'
. Requests with
http://samples/CreateOrder
will be routed to the 'createController'
.
Note that using SOAP Action headers is SOAP 1.1-specific, so it cannot be used when using Plain Old XML, nor with SOAP 1.2.
As explained in Section 5.3.3, “@Endpoint
”, the @Endpoint
style
allows you to handle multiple requests in one endpoint class. This is the responsibility of the
MethodEndpointMapping
. Similar to the endpoint mapping described above, this
mapping determines which method is to be invoked for an incoming request message.
There are two endpoint mappings that can direct requests to methods: the
PayloadRootAnnotationMethodEndpointMapping
and the
SoapActionAnnotationMethodEndpointMapping
, both of which are very similar to
their non-method counterparts described above.
The PayloadRootAnnotationMethodEndpointMapping
uses the
@PayloadRoot
annotation, with the localPart
and
namespace
elements, to mark methods with a particular qualified
name. Whenever a message comes in which has this qualified name for the payload root element, the
method will be invoked. For an example, see above.
Alternatively, the SoapActionAnnotationMethodEndpointMapping
uses the
@SoapAction
annotation to mark methods with a particular SOAP Action.
Whenever a message comes in which has this SOAPAction
header, the
method will be invoked.
The endpoint mapping mechanism has the notion of endpoint interceptors. These can be extremely useful when you want to apply specific functionality to certain requests, for example, dealing with security-related SOAP headers, or the logging of request and response message.
Interceptors located in the endpoint mapping must implement the
EndpointInterceptor
interface from the
org.springframework.ws.server package. This interface defines three methods, one that
can be used for handling the request message before the actual
endpoint will be executed, one that can be used for handling a normal response message, and one that
can be used for handling fault messages, both of which will be called after the
endpoint is executed. These three methods should provide enough flexibility to do all kinds of
pre- and post-processing.
The handleRequest(..)
method on the interceptor returns a boolean value. You
can use this method to interrupt or continue the processing of the invocation chain. When this method
returns true
, the endpoint execution chain will continue, when it returns
false
, the MessageDispatcher
interprets this to mean that
the interceptor itself
has taken care of things and does not continue executing the other interceptors and the actual endpoint
in the invocation chain. The handleResponse(..)
and
handleFault(..)
methods also have a boolean return value. When these methods
return false
, the response will not be sent back to the client.
There are a number of standard EndpointInterceptor
implementations you
can use in your Web service. Additionally, there is the XwsSecurityInterceptor
,
which is described in Section 7.2, “XwsSecurityInterceptor
”.
When developing a Web service, it can be useful to log the incoming and outgoing XML messages.
SWS facilitates this with the PayloadLoggingInterceptor
and
SoapEnvelopeLoggingInterceptor
classes. The former logs just the payload of
the message to the Commons Logging Log; the latter logs the entire SOAP envelope, including SOAP
headers. The following example shows you how to define them in an endpoint mapping:
<beans> <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="interceptors"> <list> <ref bean="loggingInterceptor"/> </list> </property> <property name="mappings"> <props> <prop key="{http://samples}orderRequest">getOrderEndpoint</prop> <prop key="{http://samples}order">createOrderEndpoint</prop> </props> </property> </bean> <bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/> </beans>
Both of these interceptors have two properties: 'logRequest' and
'logResponse', which can be set to false
to disable logging
for either request or response messages.
One of the benefits of using a contract-first development style is that we can use the schema to
validate incoming and outgoing XML messages. Spring-WS facilitates this with the
PayloadValidatingInterceptor
. This interceptor requires a reference to one
or more W3C XML or RELAX NG schemas, and can be set to validate requests or responses, or both.
Note that request validation may sound like a good idea, but makes the resulting Web service very strict. Usually, it is not really important whether the request validates, only if the endpoint can get sufficient information to fullfill a request. Validating the response is a good idea, because the endpoint should adhere to its schema. Remember Postel's Law: “Be conservative in what you do; be liberal in what you accept from others.”
Here is an example that uses the PayloadValidatingInterceptor
; in this
example, we use the schema in /WEB-INF/orders.xsd
to validate the response, but
not the request. Note that the PayloadValidatingInterceptor
can also accept
multiple schemas using the schemas property.
<bean id="validatingInterceptor" class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor"> <property name="schema" value="/WEB-INF/orders.xsd"/> <property name="validateRequest" value="false"/> <property name="validateResponse" value="true"/> </bean>
To transform the payload to another XML format, Spring Web Services offers the
PayloadTransformingInterceptor
. This endpoint interceptor is based on XSLT
stylesheets, and is especially useful when supporting multiple versions of a Web service:
you can transform the older message format to the newer format. Here is an example to use the
PayloadTransformingInterceptor
:
<bean id="transformingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor"> <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/> <property name="requestXslt" value="/WEB-INF/oldResponses.xslt"/> </bean>
We are simply transforming requests using /WEB-INF/oldRequests.xslt
, and
response messages using /WEB-INF/oldResponses.xslt
. Note that, since
endpoint interceptors are registered at the endpoint mapping level, you can simply create a
endpoint mapping that applies to the "old style" messages, and add the interceptor to that mapping.
Hence, the transformation will apply only to these "old style" message.
Spring-WS provides EndpointExceptionResolvers
to ease the pain of unexpected
exceptions occurring while your message is being processed by an endpoint which matched the request.
Endpoint exception resolvers somewhat resemble the exception mappings that can be
defined in the web application descriptor web.xml
.
However, they provide a more flexible way to handle exceptions. They provide information about what
endpoint was invoked when the exception was thrown. Furthermore, a programmatic way of handling exceptions
gives you many more options for how to respond appropriately. Rather than expose the innards of your
application by giving an exception and stack trace, you can handle the exception any way you want, for
example by returning a SOAP fault with a specific fault code and string.
Endpoint exception resolvers are automatically picked up by the MessageDispatcher
,
so no explicit configuration is necessary.
Besides implementing the EndpointExceptionResolver
interface, which is only a
matter of implementing the resolveException(MessageContext, endpoint, Exception)
method, you may also use one of the provided implementations.
The simplest implementation is the SimpleSoapExceptionResolver
, which just
creates a SOAP 1.1 Server or SOAP 1.2 Receiver Fault, and uses the exception message as the fault string.
The SimpleSoapExceptionResolver
is the default, but it can be overriden by
explicitly adding another resolver.
The SoapFaultMappingExceptionResolver
is a more sophisticated implementation.
This resolver enables you to take the class name of any exception that might be thrown and map it to a
SOAP Fault, like so:
<beans> <bean id="exceptionResolver" class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver"> <property name="defaultFault" value="SERVER"> </property> <property name="exceptionMappings"> org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request </property> </bean> </beans>
The key values and default endpoint use the format faultCode,faultString,locale
, where
only the fault code is required. If the fault string is not set, it will default to the exception message.
If the language is not set, it will default to English. The above configuration will map exceptions of
type ValidationFailureException
to a client-side SOAP Fault with a fault string
"Invalid request"
, as can be seen in the following response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Invalid request</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
If any other exception occurs, it will return the default fault: a server-side fault with the exception message as fault string.
Finally, it is also possible to annotate exception classes with the
@SoapFault
annotation, to indicate the SOAP Fault that should be returned
whenever that exception is thrown. In order for these annotations to be picked up, you need to add the
SoapFaultAnnotationExceptionResolver
to your application context.
The elements of the annotation include a fault code enumeration, fault string or reason, and language.
Here is an example exception:
package samples; import org.springframework.ws.soap.server.endpoint.annotation.FaultCode; import org.springframework.ws.soap.server.endpoint.annotation.SoapFault; @SoapFault(faultCode = FaultCode.SERVER) public class MyBusinessException extends Exception { public MyClientException(String message) { super(message); } }
Whenever the MyBusinessException
is thrown with the constructor string
"Oops!"
during endpoint invocation, it will result in the following response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring>Oops!</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>