This commit is contained in:
Arjen Poutsma
2007-05-18 17:24:01 +00:00
parent 0a5b45fd8d
commit 8c16222def
3 changed files with 279 additions and 80 deletions

View File

@@ -76,7 +76,7 @@
<para>
The <interfacename>SoapMessage</interfacename> is an extension of
<interfacename>WebServiceMessage</interfacename>. It contains SOAP-specific methods, such as getting
SOAP Headers, SOAP Faults, etc. Generally, your code should only not be dependent on
SOAP Headers, SOAP Faults, etc. Generally, your code should not be dependent on
<interfacename>SoapMessage</interfacename>, because the content of the SOAP Body can be obtained via
<methodname>getPayloadSource()</methodname> and <methodname>getPayloadResult()</methodname> in the
<interfacename>WebServiceMessage</interfacename>. Only when it is necessary to perform SOAP-specific
@@ -157,15 +157,6 @@
</section>
<section id="xpath">
<title>Handling XML With XPath</title>
<sidebar>
<title>What is XPath?</title>
<para>
XPath is a language for addressing parts of an XML document.
For more information about XPath, refer to the
<ulink url="http://www.w3.org/TR/xpath">XPath specification</ulink>, or read the
<ulink url="http://www.w3schools.com/xpath/">XPath tutorial</ulink>
</para>
</sidebar>
<para>
One of the best ways to handle XML is to use XPath. Quoting <xref linkend="effective-xml"/>, item 35:
</para>

View File

@@ -53,7 +53,7 @@
</note>
</section>
<section id="security-xws-security-interceptor">
<title>XwsSecurityInterceptor</title>
<title><classname>XwsSecurityInterceptor</classname></title>
<para>
The
<classname>XwsSecurityInterceptor</classname>

View File

@@ -61,7 +61,7 @@
<listitem>
<para>
An appropriate endpoint is searched for. If an endpoint is found, the invocation chain associated
with the handler (preprocessors, postprocessors, and endpoints) will be executed in order to create
with the endpoint (preprocessors, postprocessors, and endpoints) will be executed in order to create
a response.
</para>
</listitem>
@@ -328,7 +328,7 @@ public class MarshallingOrderEndpoint extends AbstractMarshallingPayloadEndpoint
returned. 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:
</para>
<programlisting><![CDATA[<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<programlisting id="server-order-request"><![CDATA[<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<orderRequest xmlns="http://samples" id="42"/>
</SOAP-ENV:Body>
@@ -357,7 +357,7 @@ public class MarshallingOrderEndpoint extends AbstractMarshallingPayloadEndpoint
<literal>marshaller</literal> bean.
</para>
</section>
<section>
<section id="server-at-endpoint">
<title><interfacename>@Endpoint</interfacename></title>
<para>
The previous two programming models were based on inheritance, and handled individual XML mesages.
@@ -427,14 +427,16 @@ public class AnnotationOrderEndpoint {
<classname>PayloadRootAnnotationMethodEndpointMapping</classname> is the mapping that detects and
handles the <interfacename>@PayloadRoot</interfacename> annotations.
</para>
<para>
As an alternative to using marshalling, we could have used <link linkend="xpath">XPath</link> to
extract the information out of the incoming XML request. Spring-WS offers another annotation for
this purpose: <interfacename>@XPathParam</interfacename>. You simply annotate a method paramters
with this annotation, and it will be bound with the evaluation of that annotation.
Here is an example:
</para>
<programlisting><![CDATA[package samples;
<section>
<title><interfacename>@XPathParam</interfacename></title>
<para>
As an alternative to using marshalling, we could have used <link linkend="xpath">XPath</link> to
extract the information out of the incoming XML request. Spring-WS offers another annotation for
this purpose: <interfacename>@XPathParam</interfacename>. You simply annotate a method paramters
with this annotation, and it will be bound with the evaluation of that annotation.
Here is an example:
</para>
<programlisting id="server-payload-root-annotation"><![CDATA[package samples;
import javax.xml.transform.Source;
@@ -457,7 +459,12 @@ public class AnnotationOrderEndpoint {
}
}]]></programlisting>
<programlisting><![CDATA[<beans>
<para>
Since we use the prefix <literal>s</literal> in our XPath expression, we must bind it to the
<uri>http://samples</uri> namespace:
</para>
<programlisting><![CDATA[<beans>
<bean id="orderEndpoint" class="samples.AnnotationOrderEndpoint">
<constructor-arg ref="orderService"/>
</bean>
@@ -466,81 +473,282 @@ public class AnnotationOrderEndpoint {
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>
<bean class="org.springframework.ws.server.endpoint.adapter.XPathParamAnnotationMethodEndpointAdapter" />
]]><emphasis role="bold"><![CDATA[<bean class="org.springframework.ws.server.endpoint.adapter.XPathParamAnnotationMethodEndpointAdapter">
<property name="namespaces">
<props>
<prop key="s">http://samples</prop>
</props>
</property>
</bean>]]></emphasis><![CDATA[
</beans>]]></programlisting>
<para>
Using the <interfacename>@XPathParam</interfacename>, you can bind to all the data types supported by
XPath:
<itemizedlist>
<listitem><para><type>boolean</type> or <classname>Boolean</classname></para></listitem>
<listitem><para><type>double</type> or <classname>Double</classname></para></listitem>
<listitem><para><classname>String</classname></para></listitem>
<listitem><para><interfacename>Node</interfacename></para></listitem>
<listitem><para><interfacename>NodeList</interfacename></para></listitem>
</itemizedlist>
</para>
<para>
Using the <interfacename>@XPathParam</interfacename>, you can bind to all the data types supported by
XPath:
<itemizedlist>
<listitem><para><type>boolean</type> or <classname>Boolean</classname></para></listitem>
<listitem><para><type>double</type> or <classname>Double</classname></para></listitem>
<listitem><para><classname>String</classname></para></listitem>
<listitem><para><interfacename>Node</interfacename></para></listitem>
<listitem><para><interfacename>NodeList</interfacename></para></listitem>
</itemizedlist>
</para>
</section>
</section>
</section>
<section id="server-endpoint-mapping">
<title>Endpoint mappings</title>
<para>
The endpoint mapping is responsible for mapping incoming messages to appropriate endpoints. It does this by
delivering a <classname>EndpointInterceptorChain</classname>, which consists of the endpoint that matches
the incoming request, and an optional list of endpoint interceptors. When a message is received by the
<classname>MessageDispatcher</classname>, it will ask the registered endpoint mappings to come up with a
appropriate <classname>HandlerExecutionChain</classname>. After that, the
<classname>MessageDispatcher</classname> will invoke the endpoint and interceptors in the chain.
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
<classname>PayloadRootQNameEndpointMapping</classname> or the
<classname>SoapActionEndpointMapping</classname>, but let's first examine the general concept of a
<interfacename>EndpointMapping</interfacename>.
</para>
<para>
Most endpoint mappings inherit from the <classname>AbstractEndpointMapping</classname>, which offers the
following properties:
<informaltable>
<tgroup cols="2">
<tbody>
<row>
<entry>
<methodname>interceptors</methodname>
</entry>
<entry>
the list of interceptors use. <interfacename>EndpointInterceptor</interfacename>s are
discussed in <xref linkend="ws-endpoint-interceptor"/>.
</entry>
</row>
<row>
<entry>
<methodname>defaultHandler</methodname>
</entry>
<entry>
the default handler to use. This endpoint will be returned if no specific mapping was
found.
</entry>
</row>
<row>
<entry>
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
A <interfacename>EndpointMapping</interfacename> delivers a <classname>EndpointInvocationChain</classname>,
which contains the endpoint that matches the incoming request, and may also contain a list of endpoint
interceptors that are applied to the request and response. When a request comes in, the
<classname>MessageDispatcher</classname> will hand it over to the endpoint mapping to let it inspect the
request and come up with an appropriate <classname>EndpointInvocationChain</classname>. Then
the <classname>MessageDispatcher</classname> will invoce the endpoint and any interceptors in the chain.
</para>
<para>
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 <interfacename>EndpointMapping</interfacename>s. Think of a custom endpoint mapping that
chooses an endpoint not only based on the contents of a message, but also a specific SOAP headers.
</para>
<para>
Most endpoint mappings inherit from the <classname>AbstractEndpointMapping</classname>, which offers a
<property>interceptors</property> property, which is the list of interceptors to use.
<interfacename>EndpointInterceptor</interfacename>s are discussed in
<xref linkend="server-endpoint-interceptor"/>. Additionally, there is the
<property>defaultEndpoint</property>, which is the default endpoint to use, when this endpoint mapping does
not result in a matching endpoint.
</para>
<section>
<title>SoapActionEndpointMapping</title>
<para/>
<title><classname>PayloadRootQNameEndpointMapping</classname></title>
<para>
The <classname>PayloadRootQNameEndpointMapping</classname> 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 <emphasis>namespace URI</emphasis> and a <emphasis>local part</emphasis>, the combination of which
should be unique within the mapping. Here is an example:
</para>
<programlisting><![CDATA[<beans>
]]><lineannotation>&lt;!-- no <literal>'id'</literal> required, <interfacename>EndpointMapping</interfacename> beans are automatically detected by the <classname>MessageDispatcher</classname> --&gt;</lineannotation><![CDATA[
<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>]]></programlisting>
<para>
The qualified name is expressed as <literal>{</literal> + namespace URI + <literal>}</literal> +
local part. Thus, the endpoint mapping above routes requests for which have a payload root element with
namespace <uri>http://samples</uri> and local part <literal>orderRequest</literal> to the
<literal>'getOrderEndpoint'</literal>. Requests with a local part <literal>order</literal> will
be routed to the <literal>'createController'</literal>. As a result of this mapping, the SOAP message
shown <link linkend="server-order-request">above</link> will be mapped to the
<literal>getOrderEndpoint</literal>.
</para>
</section>
<section>
<title>PayloadRootQNameEndpointMapping</title>
<para/>
<title><classname>SoapActionEndpointMapping</classname></title>
<para>
Rather than base the routing on the contents of the message with the
<classname>PayloadRootQNameEndpointMapping</classname>, you can use the <literal>SOAPAction</literal>
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 <literal>SOAPAction</literal>
unique per operation, you can use it a a discriminator. Here is an example:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
The mapping above routes requests which have the a <literal>SOAPAction</literal> of
<uri>http://samples/RequestOrder</uri> to the <literal>'getOrderEndpoint'</literal>. Requests with
<uri>http://samples/CreateOrder</uri> will be routed to the <literal>'createController'</literal>.
Note that using SOAP Action headers is SOAP-specific, so it cannot be used when using Plain Old XML.
</para>
</section>
<section id="server-method-endpoint-mapping">
<title><classname>MethodEndpointMapping</classname></title>
<para/>
<para>
As explain in <xref linkend="server-at-endpoint"/>, the <interfacename>@Endpoint</interfacename> model
allows you to handle multiple requests in one endpoint class. This is the responsibility of the
<classname>MethodEndpointMapping</classname>. Similar to the endpoint mapping described above, the
mapping determines which method is to be invoked for an incoming request message.
</para>
<para>
There are two endpoint mappings that can direct requests to methods: the
<classname>PayloadRootAnnotationMethodEndpointMapping</classname> and the
<classname>SoapActionAnnotationMethodEndpointMapping</classname>, both of which are very similar to
their non-method counterparts described above.
</para>
<para>
The <classname>PayloadRootAnnotationMethodEndpointMapping</classname> uses the
<interfacename>@PayloadRoot</interfacename> annotation, with the <literal>localPart</literal> and
<literal>namespace</literal> 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 <link linkend="server-payload-root-annotation">above</link>.
</para>
<para>
Alternatively, the <classname>SoapActionAnnotationMethodEndpointMapping</classname> uses the
<interfacename>@SoapAction</interfacename> annotation to mark methods with a particular SOAP Action.
Whenever a message comes in which has this <literal>SOAPAction</literal> header, the
method will be invoked.
</para>
</section>
<section id="server-endpoint-interceptor">
<title>Adding <interfacename>EndpointInterceptors</interfacename></title>
<para/>
<title>Intecepting requests - the <interfacename>EndpointInterceptor</interfacename> interface</title>
<para>
The endpoint mapping mechanism has the notion of endpoint interceptors, that can be extremely useful
when you want to apply specific functionality to certain requests, for example, dealing with
security-related SOAP headers, or logging the request and response message.
</para>
<para>
Interceptors located in the endpoint mapping must implement
<interfacename>EndpointInterceptor</interfacename> from the
<package>org.springframework.ws.server</package> package. This interface defines three methods, one that
can be used for handling the request message has been determined, <emphasis>before</emphasis> 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 <emphasis>after</emphasis> the
endpoint is executed. These three methods should provide enough flexibility to do all kinds of
pre- and post-processing.
</para>
<para>
The <methodname>handleRequest(..)</methodname> methods on the interceptor returns a boolean value. You
can use this method to break or continue the processing of the invocation chain. When this method
returns <literal>true</literal>, the endpoint execution chain will continue, when it returns
<literal>false</literal>, the <classname>MessageDispatcher</classname> assumes the interceptor itself
has taken care of things and does not continue executing the other interceptors and the actual endoint
in the invocation chain. The <methodname>handleResponse(..)</methodname> and
<methodname>handleFault(..)</methodname> methods also have a boolean return value. When these methods
return <literal>false</literal>, the response will not be sent back to the client.
</para>
<para>
There are a number of standard <interfacename>EndpointInterceptor</interfacename> implementations you
can use in your Web service. Additionally, there is the <classname>XwsSecurityInterceptor</classname>,
which is described in <xref linkend="security-xws-security-interceptor"/>.
</para>
<section>
<title><classname>PayloadLoggingInterceptor</classname> and
<classname>SoapEnvelopeLoggingInterceptor</classname></title>
<para>
When developing a Web service, it can be useful to log the incoming and outgoing XML messages to
the log. Spring Web Services facilitates this with the
<classname>PayloadLoggingInterceptor</classname> and the
<classname>SoapEnvelopeLoggingInterceptor</classname>. The former just logs the payload of the
message to the Commons Logging Log; the latter logs the entire SOAP Envelope, including SOAP
headers. This example shows you how to define them in an endpoint mapping:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
Both of these interceptors have two properties: <property>logRequest</property> and
<property>logResponse</property>, which can be set to <literal>false</literal> to disable logging
for either request of response messages.
</para>
</section>
<section>
<title><classname>PayloadValidatingInterceptor</classname></title>
<para>
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
<classname>PayloadValidatingInterceptor</classname>. 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.
</para>
<note>
<para>
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
<emphasis>is</emphasis> a good idea, because the endpoint should adhere to adhere to its schema.
Remember Postel's Law:
<quote>Be conservative in what you do; be liberal in what you accept from others.</quote>
</para>
</note>
<para>
Here is an example that uses the <classname>PayloadValidatingInterceptor</classname>:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
In this example, we use the schema in <filename>/WEB-INF/orders.xsd</filename> to validate the
response, but not the request.
</para>
</section>
<section>
<title><classname>PayloadTransformingInterceptor</classname></title>
<para>
To transform the payload to another XML format, Spring Web Services offers the
<classname>PayloadTransformingInterceptor</classname>. This endpoint interceptor is based on XSLT
stylesheets, and is especially useful when supporting with multiple version of a Web service:
you simply can transform the older message format to the new format.
Here is an example to use the <classname>PayloadTransformingInterceptor</classname>:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
We are simply transforming requests using <filename>/WEB-INF/oldRequests.xslt</filename>, and
response messages using <filename>/WEB-INF/oldResponses.xslt</filename>. 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 only apply to these "old style" message.
</para>
</section>
</section>
</section>
<section id="server-endpoint-exception-resolver">