Port the build to Gradle
Closes gh-1457
7
spring-ws-docs/src/docs/asciidoc/bibliography.adoc
Normal file
@@ -0,0 +1,7 @@
|
||||
[bibliography]
|
||||
= Bibliography
|
||||
|
||||
- [[[waldo-94]]] Jim Waldo, Ann Wollrath, and Sam Kendall. _A Note on Distributed Computing_. Springer Verlag. 1994
|
||||
- [[[alpine]]] Steve Loughran & Edmund Smith. _Rethinking the Java SOAP Stack_. May 17, 2005. (C) 2005 IEEE Telephone Laboratories, Inc.
|
||||
- [[[effective-enterprise-java]]] Ted Neward. Scott Meyers. _Effective Enterprise Java_. Addison-Wesley. 2004
|
||||
- [[[effective-xml]]] Elliotte Rusty Harold. Scott Meyers. _Effective XML_. Addison-Wesley. 2004
|
||||
533
spring-ws-docs/src/docs/asciidoc/client.adoc
Normal file
@@ -0,0 +1,533 @@
|
||||
[[client]]
|
||||
= Using Spring Web Services on the Client
|
||||
|
||||
Spring-WS provides a client-side Web service API that allows for consistent, XML-driven access to web services. It also caters to the use of marshallers and unmarshallers so that your service-tier code can deal exclusively with Java objects.
|
||||
|
||||
The `org.springframework.ws.client.core` package provides the core functionality for using the client-side access API. It contains template classes that simplify the use of Web services, much like the core Spring `JdbcTemplate` does for JDBC. The design principle common to Spring template classes is to provide helper methods to perform common operations and, for more sophisticated usage, delegate to user implemented callback interfaces. The web service template follows the same design. The classes offer various convenience methods for
|
||||
|
||||
* Sending and receiving of XML messages
|
||||
* Marshalling objects to XML before sending
|
||||
* Allowing for multiple transport options
|
||||
|
||||
== Using the Client-side API
|
||||
|
||||
This section describs how to use the client-side API. For how to use the server-side API, see <<server>>.
|
||||
|
||||
[[client-web-service-template]]
|
||||
=== `WebServiceTemplate`
|
||||
|
||||
The `WebServiceTemplate` is the core class for client-side web service access in Spring-WS. It contains methods for sending `Source` objects and receiving response messages as either `Source` or `Result`. Additionally, it can marshal objects to XML before sending them across a transport and unmarshal any response XML into an object again.
|
||||
|
||||
[[client-transports]]
|
||||
==== URIs and Transports
|
||||
|
||||
The `WebServiceTemplate` class uses an URI as the message destination. You can either set a `defaultUri` property on the template itself or explicitly supply a URI when calling a method on the template. The URI is resolved into a `WebServiceMessageSender`, which is responsible for sending the XML message across a transport layer. You can set one or more message senders by using the `messageSender` or `messageSenders` properties of the `WebServiceTemplate` class.
|
||||
|
||||
===== HTTP transports
|
||||
|
||||
There are two implementations of the `WebServiceMessageSender` interface for sending messages over HTTP. The default implementation is the `HttpUrlConnectionMessageSender`, which uses the facilities provided by Java itself. The alternative is the `HttpComponentsMessageSender`, which uses the https://hc.apache.org/httpcomponents-client-ga[Apache HttpComponents HttpClient]. Use the latter if you need more advanced and easy-to-use functionality (such as authentication, HTTP connection pooling, and so forth).
|
||||
|
||||
To use the HTTP transport, either set the `defaultUri` to something like `http://example.com/services` or supply the `uri` parameter for one of the methods.
|
||||
|
||||
The following example shows how to use default configuration for HTTP transports:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
|
||||
|
||||
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
|
||||
<constructor-arg ref="messageFactory"/>
|
||||
<property name="defaultUri" value="http://example.com/WebService"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows how to override the default configuration and how to use Apache HttpClient to authenticate with HTTP authentication:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
|
||||
<constructor-arg ref="messageFactory"/>
|
||||
<property name="messageSender">
|
||||
<bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
|
||||
<property name="credentials">
|
||||
<bean class="org.apache.http.auth.UsernamePasswordCredentials">
|
||||
<constructor-arg value="john:secret"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="defaultUri" value="http://example.com/WebService"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
===== JMS transport
|
||||
|
||||
For sending messages over JMS, Spring Web Services provides `JmsMessageSender`. This class uses the facilities of the Spring framework to transform the `WebServiceMessage` into a JMS `Message`, send it on its way on a `Queue` or `Topic`, and receive a response (if any).
|
||||
|
||||
To use `JmsMessageSender`, you need to set the `defaultUri` or `uri` parameter to a JMS URI, which -- at a minimum -- consists of the `jms:` prefix and a destination name. Some examples of JMS URIs are: `jms:SomeQueue`, `jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT`, and `jms:RequestQueue?replyToName=ResponseName`. For more information on this URI syntax, see the https://docs.spring.io/spring-ws/docs/current/api/org/springframework/ws/transport/jms/JmsMessageSender.html[Javadoc for `JmsMessageSender`].
|
||||
|
||||
By default, the `JmsMessageSender` sends JMS `BytesMessage`, but you can override this to use `TextMessages` by using the `messageType` parameter on the JMS URI -- for example, `jms:Queue?messageType=TEXT_MESSAGE`. Note that `BytesMessages` are the preferred type, because `TextMessages` do not support attachments and character encodings reliably.
|
||||
|
||||
The following example shows how to use the JMS transport in combination with an ActiveMQ connection factory:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
|
||||
|
||||
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
||||
<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
|
||||
</bean>
|
||||
|
||||
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
|
||||
<constructor-arg ref="messageFactory"/>
|
||||
<property name="messageSender">
|
||||
<bean class="org.springframework.ws.transport.jms.JmsMessageSender">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
===== Email Transport
|
||||
|
||||
Spring Web Services also provides an email transport, which you can use to send web service messages over SMTP and retrieve them over either POP3 or IMAP. The client-side email functionality is contained in the `MailMessageSender` class. This class creates an email message from the request `WebServiceMessage` and sends it over SMTP. It then waits for a response message to arrive at the incoming POP3 or IMAP server.
|
||||
|
||||
To use the `MailMessageSender`, set the `defaultUri` or `uri` parameter to a `mailto` URI -- for example, `mailto:john@example.com` or `mailto:server@localhost?subject=SOAP%20Test`. Make sure that the message sender is properly configured with a `transportUri`, which indicates the server to use for sending requests (typically a SMTP server), and a `storeUri`, which indicates the server to poll for responses (typically a POP3 or IMAP server).
|
||||
|
||||
The following example shows how to use the email transport:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
|
||||
|
||||
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
|
||||
<constructor-arg ref="messageFactory"/>
|
||||
<property name="messageSender">
|
||||
<bean class="org.springframework.ws.transport.mail.MailMessageSender">
|
||||
<property name="from" value="Spring-WS SOAP Client <client@example.com>"/>
|
||||
<property name="transportUri" value="smtp://client:s04p@smtp.example.com"/>
|
||||
<property name="storeUri" value="imap://client:s04p@imap.example.com/INBOX"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="defaultUri" value="mailto:server@example.com?subject=SOAP%20Test"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
===== XMPP Transport
|
||||
|
||||
Spring Web Services 2.0 introduced an XMPP (Jabber) transport, which you can use to send and receive web service messages over XMPP. The client-side XMPP functionality is contained in the `XmppMessageSender` class. This class creates an XMPP message from the request `WebServiceMessage` and sends it over XMPP. It then listens for a response message to arrive.
|
||||
|
||||
To use the `XmppMessageSender`, set the `defaultUri` or `uri` parameter to a `xmpp` URI -- for example, `xmpp:johndoe@jabber.org`. The sender also requires an `XMPPConnection` to work, which can be conveniently created by using the `org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean`.
|
||||
|
||||
The following example shows how to use the XMPP transport:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
|
||||
|
||||
<bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
|
||||
<property name="host" value="jabber.org"/>
|
||||
<property name="username" value="username"/>
|
||||
<property name="password" value="password"/>
|
||||
</bean>
|
||||
|
||||
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
|
||||
<constructor-arg ref="messageFactory"/>
|
||||
<property name="messageSender">
|
||||
<bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
|
||||
<property name="connection" ref="connection"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="defaultUri" value="xmpp:user@jabber.org"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
==== Message factories
|
||||
|
||||
In addition to a message sender, the `WebServiceTemplate` requires a web service message factory. There are two message factories for SOAP: `SaajSoapMessageFactory` and `AxiomSoapMessageFactory`. If no message factory is specified (by setting the `messageFactory` property), Spring-WS uses the `SaajSoapMessageFactory` by default.
|
||||
|
||||
=== Sending and Receiving a `WebServiceMessage`
|
||||
|
||||
The `WebServiceTemplate` contains many convenience methods to send and receive web service messages. There are methods that accept and return a `Source` and those that return a `Result`. Additionally, there are methods that marshal and unmarshal objects to XML. The following example sends a simple XML message to a web service:
|
||||
|
||||
====
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
import java.io.StringReader;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.springframework.ws.WebServiceMessageFactory;
|
||||
import org.springframework.ws.client.core.WebServiceTemplate;
|
||||
import org.springframework.ws.transport.WebServiceMessageSender;
|
||||
|
||||
public class WebServiceClient {
|
||||
|
||||
private static final String MESSAGE =
|
||||
"<message xmlns=\"http://tempuri.org\">Hello, Web Service World</message>";
|
||||
|
||||
private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
|
||||
|
||||
public void setDefaultUri(String defaultUri) {
|
||||
webServiceTemplate.setDefaultUri(defaultUri);
|
||||
}
|
||||
|
||||
_// send to the configured default URI_
|
||||
public void simpleSendAndReceive() {
|
||||
StreamSource source = new StreamSource(new StringReader(MESSAGE));
|
||||
StreamResult result = new StreamResult(System.out);
|
||||
webServiceTemplate.sendSourceAndReceiveToResult(source, result);
|
||||
}
|
||||
|
||||
_// send to an explicit URI_
|
||||
public void customSendAndReceive() {
|
||||
StreamSource source = new StreamSource(new StringReader(MESSAGE));
|
||||
StreamResult result = new StreamResult(System.out);
|
||||
webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/AnotherWebService",
|
||||
source, result);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans">
|
||||
|
||||
<bean id="webServiceClient" class="WebServiceClient">
|
||||
<property name="defaultUri" value="http://localhost:8080/WebService"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example uses the `WebServiceTemplate` to send a "`Hello, World`" message to the web service located at `http://localhost:8080/WebService` (in the case of the `simpleSendAndReceive()` method) and writes the result to the console. The `WebServiceTemplate` is injected with the default URI, which is used because no URI was supplied explicitly in the Java code.
|
||||
|
||||
Note that the `WebServiceTemplate` class is thread-safe once configured (assuming that all of its dependencies are also thread-safe, which is the case for all of the dependencies that ship with Spring-WS), so multiple objects can use the same shared `WebServiceTemplate` instance. The `WebServiceTemplate` exposes a zero-argument constructor and `messageFactory` and `messageSender` bean properties that you can use to construct the instance (by using a Spring container or plain Java code). Alternatively, consider deriving from Spring-WS's `WebServiceGatewaySupport` convenience base class, which exposes convenient bean properties to enable easy configuration. (You do not have to extend this base class. It is provided as a convenience class only.)
|
||||
|
||||
=== Sending and Receiving POJOs -- Marshalling and Unmarshalling
|
||||
|
||||
To facilitate the sending of plain Java objects, the `WebServiceTemplate` has a number of `send(..)` methods that take an `Object` as an argument for a message's data content. The method `marshalSendAndReceive(..)` in the `WebServiceTemplate` class delegates the conversion of the request object to XML to a `Marshaller` and the conversion of the response XML to an object to an `Unmarshaller`. (For more information about marshalling and unmarshaller, see https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#oxm-marshaller-unmarshaller[the Spring Framework reference documentation].) By using the marshallers, your application code can focus on the business object that is being sent or received and not be concerned with the details of how it is represented as XML. To use the marshalling functionality, you have to set a marshaller and an unmarshaller with the `marshaller` and `unmarshaller` properties of the `WebServiceTemplate` class.
|
||||
|
||||
=== Using `WebServiceMessageCallback`
|
||||
|
||||
To accommodate setting SOAP headers and other settings on the message, the `WebServiceMessageCallback` interface gives you access to the message after it has been created but before it is sent. The following example demonstrates how to set the SOAP action header on a message that is created by marshalling an object:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
public void marshalWithSoapActionHeader(MyObject o) {
|
||||
|
||||
webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {
|
||||
|
||||
public void doWithMessage(WebServiceMessage message) {
|
||||
((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
|
||||
}
|
||||
});
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Note that you can also use the `org.springframework.ws.soap.client.core.SoapActionCallback` to set the SOAP action header.
|
||||
|
||||
==== WS-Addressing
|
||||
|
||||
In addition to the <<server-ws-addressing,server-side WS-Addressing>> support, Spring Web Services also has support for this specification on the client-side.
|
||||
|
||||
For setting WS-Addressing headers on the client, you can use `org.springframework.ws.soap.addressing.client.ActionCallback`. This callback takes the desired action header as a parameter. It also has constructors for specifying the WS-Addressing version and a `To` header. If not specified, the `To` header defaults to the URL of the connection being made.
|
||||
|
||||
The following example sets the `Action` header to `http://samples/RequestOrder`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));
|
||||
----
|
||||
====
|
||||
|
||||
=== Using `WebServiceMessageExtractor`
|
||||
|
||||
The `WebServiceMessageExtractor` interface is a low-level callback interface that you have full control over the process to extract an `Object` from a received `WebServiceMessage`. The `WebServiceTemplate` invokes the `extractData(..)` method on a supplied `WebServiceMessageExtractor` while the underlying connection to the serving resource is still open. The following example shows the `WebServiceMessageExtractor` in action:
|
||||
|
||||
====
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
public void marshalWithSoapActionHeader(final Source s) {
|
||||
final Transformer transformer = transformerFactory.newTransformer();
|
||||
webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
|
||||
public void doWithMessage(WebServiceMessage message) {
|
||||
transformer.transform(s, message.getPayloadResult());
|
||||
},
|
||||
new WebServiceMessageExtractor() {
|
||||
public Object extractData(WebServiceMessage message) throws IOException {
|
||||
_// do your own transforms with message.getPayloadResult()
|
||||
// or message.getPayloadSource()_
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
== Client-side Testing
|
||||
|
||||
When it comes to testing your Web service clients (that is, classes that use the `WebServiceTemplate` to access a Web service), you have two possible approaches:
|
||||
|
||||
* Write unit tests, which mock away the `WebServiceTemplate` class, `WebServiceOperations` interface, or the complete client class.
|
||||
+
|
||||
The advantage of this approach is that it s easy to accomplish. The disadvantage is that you are not really testing the exact content of the XML messages that are sent over the wire, especially when mocking out the entire client class.
|
||||
|
||||
* Write integrations tests, which do test the contents of the message.
|
||||
|
||||
The first approach can easily be accomplished with mocking frameworks, such as EasyMock, JMock, and others. The next section focuses on writing integration tests, using the test features introduced in Spring Web Services 2.0.
|
||||
|
||||
=== Writing Client-side Integration Tests
|
||||
|
||||
Spring Web Services 2.0 introduced support for creating Web service client integration tests. In this context, a client is a class that uses the `WebServiceTemplate` to access a web service.
|
||||
|
||||
The integration test support lives in the `org.springframework.ws.test.client` package. The core class in that package is the `MockWebServiceServer`. The underlying idea is that the web service template connects to this mock server and sends it a request message, which the mock server then verifies against the registered expectations. If the expectations are met, the mock server then prepares a response message, which is sent back to the template.
|
||||
|
||||
The typical usage of the `MockWebServiceServer` is: .
|
||||
|
||||
. Create a `MockWebServiceServer` instance by calling `MockWebServiceServer.createServer(WebServiceTemplate)`, `MockWebServiceServer.createServer(WebServiceGatewaySupport)`, or `MockWebServiceServer.createServer(ApplicationContext)`.
|
||||
. Set up request expectations by calling `expect(RequestMatcher)`, possibly by using the default `RequestMatcher` implementations provided in `RequestMatchers` (which can be statically imported). Multiple expectations can be set up by chaining `andExpect(RequestMatcher)` calls.
|
||||
. Create an appropriate response message by calling `andRespond(ResponseCreator)`, possibly by using the default `ResponseCreator` implementations provided in `ResponseCreators` (which can be statically imported).
|
||||
. Use the `WebServiceTemplate` as normal, either directly of through client code.
|
||||
. Call `MockWebServiceServer.verify()` to make sure that all expectations have been met.
|
||||
|
||||
NOTE: Note that the `MockWebServiceServer` (and related classes) offers a 'fluent' API, so you can typically use the code-completion features in your IDE to guide you through the process of setting up the mock server.
|
||||
|
||||
NOTE: Also note that you can rely on the standard logging features available in Spring Web Services in your unit tests. Sometimes, it might be useful to inspect the request or response message to find out why a particular tests failed. See <<logging>> for more information.
|
||||
|
||||
Consider, for example, the following Web service client class:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
|
||||
|
||||
public class CustomerClient extends WebServiceGatewaySupport { //<1>
|
||||
|
||||
public int getCustomerCount() {
|
||||
CustomerCountRequest request = new CustomerCountRequest(); //<2>
|
||||
request.setCustomerName("John Doe");
|
||||
|
||||
CustomerCountResponse response =
|
||||
(CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request); //<3>
|
||||
|
||||
return response.getCustomerCount();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
<1> The `CustomerClient` extends `WebServiceGatewaySupport`, which provides it with a `webServiceTemplate` property.
|
||||
<2> `CustomerCountRequest` is an object supported by a marshaller. For instance, it can have an `@XmlRootElement` annotation to be supported by JAXB2.
|
||||
<3> The `CustomerClient` uses the `WebServiceTemplate` offered by `WebServiceGatewaySupport` to marshal the request object into a SOAP message and sends that to the web service. The response object is unmarshalled into a `CustomerCountResponse`.
|
||||
====
|
||||
|
||||
The following example shows a typical test for `CustomerClient`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import javax.xml.transform.Source;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.xml.transform.StringSource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.springframework.ws.test.client.MockWebServiceServer; //<1>
|
||||
import static org.springframework.ws.test.client.RequestMatchers.*; //<1>
|
||||
import static org.springframework.ws.test.client.ResponseCreators.*; //<1>
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class) //<2>
|
||||
@ContextConfiguration("integration-test.xml") //<2>
|
||||
public class CustomerClientIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private CustomerClient client; //<3>
|
||||
|
||||
private MockWebServiceServer mockServer; //<4>
|
||||
|
||||
@Before
|
||||
public void createServer() throws Exception {
|
||||
mockServer = MockWebServiceServer.createServer(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customerClient() throws Exception {
|
||||
Source requestPayload = new StringSource(
|
||||
"<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
|
||||
"<customerName>John Doe</customerName>" +
|
||||
"</customerCountRequest>");
|
||||
Source responsePayload = new StringSource(
|
||||
"<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
|
||||
"<customerCount>10</customerCount>" +
|
||||
"</customerCountResponse>");
|
||||
|
||||
mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));//<5>
|
||||
|
||||
int result = client.getCustomerCount(); //<6>
|
||||
assertEquals(10, result); //<6>
|
||||
|
||||
mockServer.verify(); //<7>
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
<1> The `CustomerClientIntegrationTest` imports the `MockWebServiceServer` and statically imports `RequestMatchers` and `ResponseCreators`.
|
||||
<2> This test uses the standard testing facilities provided in the Spring Framework. This is not required but is generally the easiest way to set up the test.
|
||||
<3> The `CustomerClient` is configured in `integration-test.xml` and wired into this test using `@Autowired`.
|
||||
<4> In a `@Before` method, we create a `MockWebServiceServer` by using the `createServer` factory method.
|
||||
<5> We define expectations by calling `expect()` with a `payload()` `RequestMatcher` provided by the statically imported `RequestMatchers` (see <<client-test-request-matcher>>).
|
||||
+
|
||||
We also set up a response by calling `andRespond()` with a `withPayload()` `ResponseCreator` provided by the statically imported `ResponseCreators` (see <<client-test-response-creator>>).
|
||||
+
|
||||
This part of the test might look a bit confusing, but the code-completion features of your IDE are of great help. After you type `expect(`, your IDE can provide you with a list of possible request matching strategies, provided you statically imported `RequestMatchers`. The same applies to `andRespond(`, provided you statically imported `ResponseCreators`.
|
||||
<6> We call `getCustomerCount()` on the `CustomerClient`, thus using the `WebServiceTemplate`. The template has been set up for "`testing mode`" by now, so no real (HTTP) connection is made by this method call. We also make some JUnit assertions based on the result of the method call.
|
||||
<7> We call `verify()` on the `MockWebServiceServer`, verifying that the expected message was actually received.
|
||||
====
|
||||
|
||||
[[client-test-request-matcher]]
|
||||
=== Using `RequestMatcher` and `RequestMatchers`
|
||||
|
||||
To verify whether the request message meets certain expectations, the `MockWebServiceServer` uses the `RequestMatcher` strategy interface. The contract defined by this interface is as follows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface RequestMatcher {
|
||||
|
||||
void match(URI uri,
|
||||
WebServiceMessage request)
|
||||
throws IOException,
|
||||
AssertionError;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can write your own implementations of this interface, throwing `AssertionError` exceptions when the message does not meet your expectations, but you certainly do not have to. The `RequestMatchers` class provides standard `RequestMatcher` implementations for you to use in your tests. You typically statically import this class.
|
||||
|
||||
The `RequestMatchers` class provides the following request matchers:
|
||||
|
||||
[cols="2", options="header"]
|
||||
|===
|
||||
| `RequestMatchers` method
|
||||
| Description
|
||||
|
||||
| `anything()`
|
||||
| Expects any sort of request.
|
||||
|
||||
| `payload()`
|
||||
| Expects a given request payload. May include https://github.com/xmlunit/user-guide/wiki/Placeholders[XMLUnit Placeholders]
|
||||
|
||||
| `validPayload()`
|
||||
| Expects the request payload to validate against given XSD schemas.
|
||||
|
||||
| `xpath()`
|
||||
| Expects a given XPath expression to exist, not exist, or evaluate to a given value.
|
||||
|
||||
| `soapHeader()`
|
||||
| Expects a given SOAP header to exist in the request message.
|
||||
|
||||
| `soapEnvelope()`
|
||||
| Expects a given SOAP payload. May include https://github.com/xmlunit/user-guide/wiki/Placeholders[XMLUnit Placeholders]
|
||||
|
||||
| `connectionTo()`
|
||||
| Expects a connection to the given URL.
|
||||
|===
|
||||
|
||||
You can set up multiple request expectations by chaining `andExpect()` calls:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
mockServer.expect(connectionTo("http://example.com")).
|
||||
andExpect(payload(expectedRequestPayload)).
|
||||
andExpect(validPayload(schemaResource)).
|
||||
andRespond(...);
|
||||
----
|
||||
====
|
||||
|
||||
For more information on the request matchers provided by `RequestMatchers`, see the https://docs.spring.io/spring-ws/docs/current/api/org/springframework/ws/test/client/RequestMatchers.html[Javadoc].
|
||||
|
||||
[[client-test-response-creator]]
|
||||
=== Using `ResponseCreator` and `ResponseCreators`
|
||||
|
||||
When the request message has been verified and meets the defined expectations, the `MockWebServiceServer` creates a response message for the `WebServiceTemplate` to consume. The server uses the `ResponseCreator` strategy interface for this purpose:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface ResponseCreator {
|
||||
|
||||
WebServiceMessage createResponse(URI uri,
|
||||
WebServiceMessage request,
|
||||
WebServiceMessageFactory messageFactory)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Once again, you can write your own implementations of this interface, creating a response message by using the message factory, but you certainly do not have to, as the `ResponseCreators` class provides standard `ResponseCreator` implementations for you to use in your tests. You typically statically import this class.
|
||||
|
||||
The `ResponseCreators` class provides the following responses:
|
||||
|
||||
[cols="2", options="header"]
|
||||
|===
|
||||
| `ResponseCreators` method
|
||||
| Description
|
||||
|
||||
| `withPayload()`
|
||||
| Creates a response message with a given payload.
|
||||
|
||||
| `withError()`
|
||||
| Creates an error in the response connection. This method gives you the opportunity to test your error handling.
|
||||
|
||||
| `withException()`
|
||||
| Throws an exception when reading from the response connection. This method gives you the opportunity to test your exception handling.
|
||||
|
||||
| `withMustUnderstandFault()`, `withClientOrSenderFault()`, `withServerOrReceiverFault()`, or `withVersionMismatchFault()`
|
||||
| Creates a response message with a given SOAP fault. This method gives you the opportunity to test your Fault handling.
|
||||
|===
|
||||
|
||||
For more information on the request matchers provided by `RequestMatchers`, see the https://docs.spring.io/spring-ws/docs/current/api/org/springframework/ws/test/client/RequestMatchers.html[Javadoc].
|
||||
323
spring-ws-docs/src/docs/asciidoc/common.adoc
Normal file
@@ -0,0 +1,323 @@
|
||||
:toclevels: 10
|
||||
|
||||
[[common]]
|
||||
= Shared components
|
||||
|
||||
This chapter explores the components that are shared between client- and server-side Spring-WS development. These interfaces and classes represent the building blocks of Spring-WS, so you need to understand what they do, even if you do not use them directly.
|
||||
|
||||
[[web-service-messages]]
|
||||
== Web Service Messages
|
||||
|
||||
This section describes the messages and message factories that Spring-WS uses.
|
||||
|
||||
[[web-service-message]]
|
||||
=== `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:
|
||||
|
||||
[cols="2", options="header"]
|
||||
|===
|
||||
| Source or Result implementation
|
||||
| Wrapped 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.
|
||||
|
||||
[[soap-message]]
|
||||
=== `SoapMessage`
|
||||
|
||||
`SoapMessage` is a subclass of `WebServiceMessage`. It contains SOAP-specific methods, such as getting SOAP Headers, SOAP Faults, and so on. Generally, your code should not be dependent on `SoapMessage`, because the content of the SOAP Body (the payload of the message) can be obtained by using `getPayloadSource()` and `getPayloadResult()` in the `WebServiceMessage`. Only when it is necessary to perform SOAP-specific actions (such as adding a header, getting an attachment, and so on) should you need to cast `WebServiceMessage` to `SoapMessage`.
|
||||
|
||||
[[message-factories]]
|
||||
=== Message Factories
|
||||
|
||||
Concrete message implementations are created by a `WebServiceMessageFactory`. This factory can create an empty message or read a message from an input stream. There are two concrete implementations of `WebServiceMessageFactory`. One is based on SAAJ, the SOAP with Attachments API for Java. The other is based on Axis 2's AXIOM (AXis Object Model).
|
||||
|
||||
==== `SaajSoapMessageFactory`
|
||||
|
||||
The `SaajSoapMessageFactory` uses the SOAP with Attachments API for Java (SAAJ) 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:
|
||||
|
||||
[cols="2", options="header"]
|
||||
|===
|
||||
| Application Server
|
||||
| SAAJ Version
|
||||
|
||||
| BEA WebLogic 8
|
||||
| 1.1
|
||||
|
||||
| BEA WebLogic 9
|
||||
| 1.1/1.2^1^
|
||||
|
||||
| IBM WebSphere 6
|
||||
| 1.2
|
||||
|
||||
| SUN Glassfish 1
|
||||
| 1.3
|
||||
|
||||
2+|^1^Weblogic 9 has a known bug in the SAAJ 1.2 implementation: it implements all the 1.2 interfaces but throws an `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 can wire up a `SaajSoapMessageFactory` as follows:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
|
||||
----
|
||||
====
|
||||
|
||||
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 performant. In that case, the `AxiomSoapMessageFactory` might be more applicable.
|
||||
|
||||
==== `AxiomSoapMessageFactory`
|
||||
|
||||
The `AxiomSoapMessageFactory` uses the AXis 2 Object Model (AXIOM) 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). Doing so causesthe contents of the SOAP body to be read directly from the socket stream. When this setting is enabled, the payload can be read only once. This means that you have to make sure that any pre-processing (logging or other work) of the message does not consume it.
|
||||
|
||||
You can use the `AxiomSoapMessageFactory` as follows:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
|
||||
<property name="payloadCaching" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
In addition to payload caching, AXIOM supports full streaming messages, as defined in the `StreamingWebServiceMessage`. This means that you can directly set the payload on the response message, rather than writing it to a DOM tree or buffer.
|
||||
|
||||
Full streaming for AXIOM is used when a handler method returns a JAXB2-supported object. It automatically sets this marshalled object into the response message and writes it out to the outgoing socket stream when the response is going out.
|
||||
|
||||
For more information about full streaming, see the class-level Javadoc for `StreamingWebServiceMessage` and `StreamingPayload`.
|
||||
|
||||
[[soap_11_or_12]]
|
||||
==== 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:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<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 preceding example, we define a `SaajSoapMessageFactory` that accepts only SOAP 1.2 messages.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
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, though 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 1.1 is currently the safest bet.
|
||||
====
|
||||
|
||||
[[message-context]]
|
||||
=== `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 <<client-web-service-template,`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`.
|
||||
|
||||
[[transport-context]]
|
||||
== `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 message content.
|
||||
|
||||
However, it is sometimes necessary to get access to the underlying transport, either on the client or the 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:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
TransportContext context = TransportContextHolder.getTransportContext();
|
||||
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
|
||||
HttpServletRequest request = connection.getHttpServletRequest();
|
||||
String ipAddress = request.getRemoteAddr();
|
||||
----
|
||||
====
|
||||
|
||||
[[xpath]]
|
||||
== Handling XML With XPath
|
||||
|
||||
One of the best ways to handle XML is to use XPath. Quoting <<effective-xml>>, item 35:
|
||||
|
||||
[quote, Elliotte Rusty Harold]
|
||||
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.
|
||||
|
||||
Spring Web Services has two ways to use XPath within your application: the faster `XPathExpression` or the more flexible `XPathTemplate`.
|
||||
|
||||
[[xpath-expression]]
|
||||
=== `XPathExpression`
|
||||
|
||||
The `XPathExpression` is an abstraction over a compiled XPath expression, such as the Java 5 `javax.xml.xpath.XPathExpression` interface or the Jaxen `XPath` class. To construct an expression in an application context, you can use `XPathExpressionFactoryBean`. The following example uses this factory bean:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<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 preceding expression does not use namespaces, but we could set those by using the `namespaces` property of the factory bean. The expression can be used in the code as follows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
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 to use it:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
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());
|
||||
}
|
||||
});
|
||||
PlainText Section qName; // do something with the list of Contact objects
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Similar to mapping rows in Spring JDBC's `RowMapper`, each result node is mapped by using an anonymous inner class. In this case, we create a `Contact` object, which we use later on.
|
||||
|
||||
[[xpath-template]]
|
||||
=== `XPathTemplate`
|
||||
|
||||
The `XPathExpression` lets you evaluate only 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`, and others). The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
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_
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[logging]]
|
||||
== 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 before it is sent. Spring Web Services offer this functionality, through the standard Commons Logging interface.
|
||||
|
||||
WARNING: 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, set the `org.springframework.ws.server.MessageTracing` logger level to `DEBUG` or `TRACE`. On the `DEBUG` level, only the payload root element is logged. On the `TRACE` level, the entire message content is logged. If you want to log only sent messages, use the `org.springframework.ws.server.MessageTracing.sent` logger. Similarly, you can use `org.springframework.ws.server.MessageTracing.received` to log only received messages.
|
||||
|
||||
On the client-side, similar loggers exist: `org.springframework.ws.client.MessageTracing.sent` and `org.springframework.ws.client.MessageTracing.received`.
|
||||
|
||||
The following example of a `log4j.properties` configuration file logs 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:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
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 is:
|
||||
|
||||
====
|
||||
----
|
||||
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] ...
|
||||
----
|
||||
====
|
||||
BIN
spring-ws-docs/src/docs/asciidoc/images/i21-banner-rhs.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/oxm-exceptions.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
48
spring-ws-docs/src/docs/asciidoc/images/oxm-exceptions.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<!-- Generated by dot version 1.13 (v16) (Mon August 23, 2004)
|
||||
For user: (arjen) Arjen Poutsma Title: G Pages: 1 -->
|
||||
<svg width="525pt" height="192pt"
|
||||
viewBox = "-1 -1 524 191"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="graph0" class="graph" style="font-family:Times-Roman;font-size:14.00;">
|
||||
<title>G</title>
|
||||
<g id="node1" class="node"><title>c1</title>
|
||||
<polygon style="fill:none;stroke:black;" points="259,40 374,40 374,4 259,4 259,40"/>
|
||||
<text text-anchor="middle" x="316" y="25" style="font-family:Arial Italic;font-size:10.00;">XmlMappingException</text>
|
||||
</g>
|
||||
<g id="node2" class="node"><title>c2</title>
|
||||
<polygon style="fill:none;stroke:black;" points="88,112 321,112 321,76 88,76 88,112"/>
|
||||
<text text-anchor="middle" x="204" y="97" style="font-family:Arial Regular;font-size:10.00;">GenericMarshallingFailureException</text>
|
||||
</g>
|
||||
<g id="edge2" class="edge"><title>c1->c2</title>
|
||||
<path style="fill:none;stroke:black;" d="M279,46C264,56 246,67 232,76"/>
|
||||
<polygon style="fill:none;stroke:black;" points="278,43 288,40 282,48 278,43"/>
|
||||
</g>
|
||||
<g id="node8" class="node"><title>c5</title>
|
||||
<polygon style="fill:none;stroke:black;" points="338,112 518,112 518,76 338,76 338,112"/>
|
||||
<text text-anchor="middle" x="428" y="97" style="font-family:Arial Regular;font-size:10.00;">ValidationFailureException</text>
|
||||
</g>
|
||||
<g id="edge8" class="edge"><title>c1->c5</title>
|
||||
<path style="fill:none;stroke:black;" d="M353,46C368,56 386,67 400,76"/>
|
||||
<polygon style="fill:none;stroke:black;" points="350,48 344,40 354,43 350,48"/>
|
||||
</g>
|
||||
<g id="node4" class="node"><title>c3</title>
|
||||
<polygon style="fill:none;stroke:black;" points="5,184 191,184 191,148 5,148 5,184"/>
|
||||
<text text-anchor="middle" x="98" y="169" style="font-family:Arial Regular;font-size:10.00;">MarshallingFailureException</text>
|
||||
</g>
|
||||
<g id="edge4" class="edge"><title>c2->c3</title>
|
||||
<path style="fill:none;stroke:black;" d="M168,118C154,128 138,139 124,148"/>
|
||||
<polygon style="fill:none;stroke:black;" points="167,115 177,112 171,120 167,115"/>
|
||||
</g>
|
||||
<g id="node6" class="node"><title>c4</title>
|
||||
<polygon style="fill:none;stroke:black;" points="209,184 413,184 413,148 209,148 209,184"/>
|
||||
<text text-anchor="middle" x="311" y="169" style="font-family:Arial Regular;font-size:10.00;">UnmarshallingFailureException</text>
|
||||
</g>
|
||||
<g id="edge6" class="edge"><title>c2->c4</title>
|
||||
<path style="fill:none;stroke:black;" d="M240,118C254,128 271,139 284,148"/>
|
||||
<polygon style="fill:none;stroke:black;" points="237,120 231,112 241,115 237,120"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/s2_box_logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/sequence.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/spring-deps.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/spring-ws-logo.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
spring-ws-docs/src/docs/asciidoc/images/xdev-spring_logo.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
55
spring-ws-docs/src/docs/asciidoc/index.adoc
Normal file
@@ -0,0 +1,55 @@
|
||||
= Spring Web Services Reference Documentation
|
||||
Arjen Poutsma, Rick Evans, Tareq Abed Rabbo, Greg Turnquist, Jay Bryant, Corneil du Plessis
|
||||
:doctype: book
|
||||
:revnumber: {gradle-project-version}
|
||||
:revdate: {localdate}
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:source-highlighter: prettify
|
||||
:sectnumlevels: 3
|
||||
|
||||
(C) 2005-2020 The original authors.
|
||||
|
||||
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
:sectnums!:
|
||||
|
||||
[[spring-framework-reference]]
|
||||
include::preface.adoc[leveloffset=+1]
|
||||
|
||||
:sectnums!:
|
||||
= I. Introduction
|
||||
:sectnums:
|
||||
|
||||
This first part of the reference documentation <<what-is-spring-ws,is an overview>> of Spring Web Services and the underlying concepts. Spring-WS is then introduced, and <<why-contract-first,the concepts>> behind contract-first web service development are explained.
|
||||
|
||||
include::what-is-spring-ws.adoc[leveloffset=+1]
|
||||
|
||||
include::why-contract-first.adoc[leveloffset=+1]
|
||||
|
||||
include::tutorial.adoc[leveloffset=+1]
|
||||
|
||||
:sectnums!:
|
||||
= II. Reference
|
||||
:sectnums:
|
||||
|
||||
This part of the reference documentation details the various components that comprise Spring Web Services. This includes <<common,a chapter>> that discusses the parts common to both client- and server-side WS, a chapter devoted to the specifics of <<server,writing server-side web services>>, a chapter about using web services on <<client,the client-side>>, and a chapter on using <<security,WS-Security>>.
|
||||
|
||||
include::common.adoc[leveloffset=+1]
|
||||
|
||||
include::server.adoc[leveloffset=+1]
|
||||
|
||||
include::client.adoc[leveloffset=+1]
|
||||
|
||||
include::security.adoc[leveloffset=+1]
|
||||
|
||||
:sectnums!:
|
||||
[[resources]]
|
||||
= III. Other Resources
|
||||
:sectnums:
|
||||
|
||||
In addition to this reference documentation, a number of other resources may help you learn how to use Spring Web Services. These additional, third-party resources are enumerated in this section.
|
||||
|
||||
:sectnums!:
|
||||
|
||||
include::bibliography.adoc[leveloffset=+1]
|
||||
7
spring-ws-docs/src/docs/asciidoc/preface.adoc
Normal file
@@ -0,0 +1,7 @@
|
||||
[preface]
|
||||
[[overview]]
|
||||
= Preface
|
||||
|
||||
In the current age of Service Oriented Architectures, more and more people use web services to connect previously unconnected systems. Initially, web services were considered to be just another way to do a Remote Procedure Call (RPC). Over time, however, people found out that there is a big difference between RPCs and web services. Especially when interoperability with other platforms is important, it is often better to send encapsulated XML documents that contain all the data necessary to process the request. Conceptually, XML-based web services are better compared to message queues than to remoting solutions. Overall, XML should be considered the platform-neutral representation of data, the _common language_ of SOA. When developing or using web services, the focus should be on this XML and not on Java.
|
||||
|
||||
Spring Web Services focuses on creating these document-driven web services. Spring Web Services facilitates contract-first SOAP service development, allowing for the creation of flexible web services by using one of the many ways to manipulate XML payloads. Spring-WS provides a powerful <<server,message dispatching framework>>, a <<security,WS-Security>> solution that integrates with your existing application security solution, and a <<client,Client-side API>> that follows the familiar Spring template pattern.
|
||||
1071
spring-ws-docs/src/docs/asciidoc/security.adoc
Normal file
1279
spring-ws-docs/src/docs/asciidoc/server.adoc
Normal file
612
spring-ws-docs/src/docs/asciidoc/tutorial.adoc
Normal file
@@ -0,0 +1,612 @@
|
||||
[[tutorial]]
|
||||
= Writing Contract-First Web Services
|
||||
|
||||
This tutorial shows you how to write <<why-contract-first,contract-first web services>> -- that is, how to develop web services that start with the XML Schema or WSDL contract first followed by the Java code second. Spring-WS focuses on this development style, and this tutorial should help you get started. Note that the first part of this tutorial contains almost no Spring-WS specific information. It is mostly about XML, XSD, and WSDL. The <<tutorial-creating-project,second part>> focuses on implementing this contract with Spring-WS .
|
||||
|
||||
The most important thing when doing contract-first web service development is tothink in terms of XML. This means that Java language concepts are of lesser importance. It is the XML that is sent across the wire, and you should focus on that. Java being used to implement the web service is an implementation detail.
|
||||
|
||||
In this tutorial, we define a web service that is created by a Human Resources department. Clients can send holiday request forms to this service to book a holiday.
|
||||
|
||||
== Messages
|
||||
|
||||
In this section, we focus on the actual XML messages that are sent to and from the web service. We start out by determining what these messages look like.
|
||||
|
||||
=== Holiday
|
||||
|
||||
In the scenario, we have to deal with holiday requests, so it makes sense to determine what a holiday looks like in XML:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<Holiday xmlns="http://mycompany.com/hr/schemas">
|
||||
<StartDate>2006-07-03</StartDate>
|
||||
<EndDate>2006-07-07</EndDate>
|
||||
</Holiday>
|
||||
----
|
||||
====
|
||||
|
||||
A holiday consists of a start date and an end date. We have also decided to use the standard https://www.cl.cam.ac.uk/~mgk25/iso-time.html[ISO 8601] date format for the dates, because that saves a lot of parsing hassle. We have also added a namespace to the element, to make sure our elements can used within other XML documents.
|
||||
|
||||
=== Employee
|
||||
|
||||
There is also the notion of an employee in the scenario. Here is what it looks like in XML:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<Employee xmlns="http://mycompany.com/hr/schemas">
|
||||
<Number>42</Number>
|
||||
<FirstName>Arjen</FirstName>
|
||||
<LastName>Poutsma</LastName>
|
||||
</Employee>
|
||||
----
|
||||
====
|
||||
|
||||
We have used the same namespace as before. If this `<Employee/>` element could be used in other scenarios, it might make sense to use a different namespace, such as `http://example.com/employees/schemas`.
|
||||
|
||||
=== HolidayRequest
|
||||
|
||||
Both the `holiday` element and the `employee` element can be put in a `<HolidayRequest/>`:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
|
||||
<Holiday>
|
||||
<StartDate>2006-07-03</StartDate>
|
||||
<EndDate>2006-07-07</EndDate>
|
||||
</Holiday>
|
||||
<Employee>
|
||||
<Number>42</Number>
|
||||
<FirstName>Arjen</FirstName>
|
||||
<LastName>Poutsma</LastName>
|
||||
</Employee>
|
||||
</HolidayRequest>
|
||||
----
|
||||
====
|
||||
|
||||
The order of the two elements does not matter: `<Employee/>` could have been the first element. What matters is that all of the data is there. In fact, the data is the only thing that is important: We take a data-driven approach.
|
||||
|
||||
[[tutorial.xsd]]
|
||||
== Data Contract
|
||||
|
||||
Now that we have seen some examples of the XML data that we can use, it makes sense to formalize this into a schema. This data contract defines the message format we accept. There are four different ways of defining such a contract for XML:
|
||||
|
||||
* DTDs
|
||||
* https://www.w3.org/XML/Schema[XML Schema (XSD)]
|
||||
* http://www.relaxng.org/[RELAX NG]
|
||||
* http://www.schematron.com/[Schematron]
|
||||
|
||||
DTDs have limited namespace support, so they are not suitable for web services. Relax NG and Schematron are easier than XML Schema. Unfortunately, they are not so widely supported across platforms. As a result, we use XML Schema.
|
||||
|
||||
By far, the easiest way to create an XSD is to infer it from sample documents. Any good XML editor or Java IDE offers this functionality. Basically, these tools use some sample XML documents to generate a schema that validates them all. The end result certainly needs to be polished up, but it is a great starting point.
|
||||
|
||||
Using the sample described earlier, we end up with the following generated schema:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://mycompany.com/hr/schemas"
|
||||
xmlns:hr="http://mycompany.com/hr/schemas">
|
||||
<xs:element name="HolidayRequest">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:Holiday"/>
|
||||
<xs:element ref="hr:Employee"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Holiday">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:StartDate"/>
|
||||
<xs:element ref="hr:EndDate"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="StartDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="EndDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="Employee">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:Number"/>
|
||||
<xs:element ref="hr:FirstName"/>
|
||||
<xs:element ref="hr:LastName"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="Number" type="xs:integer"/>
|
||||
<xs:element name="FirstName" type="xs:NCName"/>
|
||||
<xs:element name="LastName" type="xs:NCName"/>
|
||||
</xs:schema>
|
||||
----
|
||||
====
|
||||
|
||||
This generated schema can be improved. The first thing to notice is that every type has a root-level element declaration. This means that the web service should be able to accept all of these elements as data. This is not desirable: We want to accept only a `<HolidayRequest/>`. By removing the wrapping element tags (thus keeping the types) and inlining the results, we can accomplish this, as follows:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:hr="http://mycompany.com/hr/schemas"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://mycompany.com/hr/schemas">
|
||||
<xs:element name="HolidayRequest">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="Holiday" type="hr:HolidayType"/>
|
||||
<xs:element name="Employee" type="hr:EmployeeType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="HolidayType">
|
||||
<xs:sequence>
|
||||
<xs:element name="StartDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="EndDate" type="xs:NMTOKEN"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="EmployeeType">
|
||||
<xs:sequence>
|
||||
<xs:element name="Number" type="xs:integer"/>
|
||||
<xs:element name="FirstName" type="xs:NCName"/>
|
||||
<xs:element name="LastName" type="xs:NCName"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
----
|
||||
====
|
||||
|
||||
The schema still has one problem: With a schema like this, you can expect the following message to validate:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
|
||||
<Holiday>
|
||||
<StartDate>this is not a date</StartDate>
|
||||
<EndDate>neither is this</EndDate>
|
||||
</Holiday>
|
||||
PlainText Section qName:lineannotation level:4, chunks:[<, !-- ... --, >] attrs:[:]
|
||||
</HolidayRequest>
|
||||
----
|
||||
====
|
||||
|
||||
Clearly, we must make sure that the start and end date are really dates. XML Schema has an excellent built-in `date` type that we can use. We also change the `NCName` s to `string` instances. Finally, we change the `sequence` in `<HolidayRequest/>` to `all`. This tells the XML parser that the order of `<Holiday/>` and `<Employee/>` is not significant. Our final XSD now looks like the following listing:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:hr="http://mycompany.com/hr/schemas"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://mycompany.com/hr/schemas">
|
||||
<xs:element name="HolidayRequest">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Holiday" type="hr:HolidayType"/> <!--1-->
|
||||
<xs:element name="Employee" type="hr:EmployeeType"/> <!--1-->
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:complexType name="HolidayType">
|
||||
<xs:sequence>
|
||||
<xs:element name="StartDate" type="xs:date"/> <!--2-->
|
||||
<xs:element name="EndDate" type="xs:date"/> <!--2-->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="EmployeeType">
|
||||
<xs:sequence>
|
||||
<xs:element name="Number" type="xs:integer"/>
|
||||
<xs:element name="FirstName" type="xs:string"/> <!--3-->
|
||||
<xs:element name="LastName" type="xs:string"/> <!--3-->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
----
|
||||
|
||||
<1> `all` tells the XML parser that the order of `<Holiday/>` and `<Employee/>` is not significant.
|
||||
<2> We use the `xs:date` data type (which consist of a year, a month, and a day) for `<StartDate/>` and `<EndDate/>`.
|
||||
<3> `xs:string` is used for the first and last names.
|
||||
====
|
||||
|
||||
We store this file as `hr.xsd`.
|
||||
|
||||
[[tutorial-service-contract]]
|
||||
== Service Contract
|
||||
|
||||
A service contract is generally expressed as a https://www.w3.org/TR/wsdl[WSDL] file. Note that, in Spring-WS, writing the WSDL by hand is not required. Based on the XSD and some conventions, Spring-WS can create the WSDL for you, as explained in the section entitled <<tutorial-implementing-endpoint>>. The remainder of this section shows how to write WSDL by hand. You may want to skip to <<tutorial-creating-project,the next section>>.
|
||||
|
||||
We start our WSDL with the standard preamble and by importing our existing XSD. To separate the schema from the definition, we use a separate namespace for the WSDL definitions: `http://mycompany.com/hr/definitions`. The following listing shows the preamble:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
xmlns:schema="http://mycompany.com/hr/schemas"
|
||||
xmlns:tns="http://mycompany.com/hr/definitions"
|
||||
targetNamespace="http://mycompany.com/hr/definitions">
|
||||
<wsdl:types>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/>
|
||||
</xsd:schema>
|
||||
</wsdl:types>
|
||||
----
|
||||
====
|
||||
|
||||
Next, we add our messages based on the written schema types. We only have one message, the `<HolidayRequest/>` we put in the schema:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<wsdl:message name="HolidayRequest">
|
||||
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
|
||||
</wsdl:message>
|
||||
----
|
||||
====
|
||||
|
||||
We add the message to a port type as an operation:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<wsdl:portType name="HumanResource">
|
||||
<wsdl:operation name="Holiday">
|
||||
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
|
||||
</wsdl:operation>
|
||||
</wsdl:portType>
|
||||
----
|
||||
====
|
||||
|
||||
That message finishes the abstract part of the WSDL (the interface, as it were) and leaves the concrete part. The concrete part consists of a `binding` (which tells the client how to invoke the operations you have just defined) and a `service` (which tells the client where to invoke it).
|
||||
|
||||
Adding a concrete part is pretty standard. To do so, refer to the abstract part you defined previously, make sure you use `document/literal` for the `soap:binding` elements (`rpc/encoded` is deprecated), pick a `soapAction` for the operation (in this case, `http://mycompany.com/RequestHoliday`, but any URI works), and determine the `location` URL where you want the request to arrive (in this case, `http://mycompany.com/humanresources`):
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
xmlns:schema="http://mycompany.com/hr/schemas"
|
||||
xmlns:tns="http://mycompany.com/hr/definitions"
|
||||
targetNamespace="http://mycompany.com/hr/definitions">
|
||||
<wsdl:types>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<xsd:import namespace="http://mycompany.com/hr/schemas" <!--1-->
|
||||
schemaLocation="hr.xsd"/>
|
||||
</xsd:schema>
|
||||
</wsdl:types>
|
||||
<wsdl:message name="HolidayRequest"> <!--2-->
|
||||
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/> <!--3-->
|
||||
</wsdl:message>
|
||||
<wsdl:portType name="HumanResource"> <!--4-->
|
||||
<wsdl:operation name="Holiday">
|
||||
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/> <!--2-->
|
||||
</wsdl:operation>
|
||||
</wsdl:portType>
|
||||
<wsdl:binding name="HumanResourceBinding" type="tns:HumanResource"> <!--4--><!--5-->
|
||||
<soap:binding style="document" <!--6-->
|
||||
transport="http://schemas.xmlsoap.org/soap/http"/> <!--7-->
|
||||
<wsdl:operation name="Holiday">
|
||||
<soap:operation soapAction="http://mycompany.com/RequestHoliday"/> <!--8-->
|
||||
<wsdl:input name="HolidayRequest">
|
||||
<soap:body use="literal"/> <!--6-->
|
||||
</wsdl:input>
|
||||
</wsdl:operation>
|
||||
</wsdl:binding>
|
||||
<wsdl:service name="HumanResourceService">
|
||||
<wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort"> <!--5-->
|
||||
<soap:address location="http://localhost:8080/holidayService/"/> <!--9-->
|
||||
</wsdl:port>
|
||||
</wsdl:service>
|
||||
</wsdl:definitions>
|
||||
----
|
||||
|
||||
<1> We import the schema defined in <<tutorial.xsd>>.
|
||||
<2> We define the `HolidayRequest` message, which gets used in the `portType`.
|
||||
<3> The `HolidayRequest` type is defined in the schema.
|
||||
<4> We define the `HumanResource` port type, which gets used in the `binding`.
|
||||
<5> We define the `HumanResourceBinding` binding, which gets used in the `port`.
|
||||
<6> We use a document/literal style.
|
||||
<7> The literal `http://schemas.xmlsoap.org/soap/http` signifies a HTTP transport.
|
||||
<8> The `soapAction` attribute signifies the `SOAPAction` HTTP header that will be sent with every request.
|
||||
<9> The `http://localhost:8080/holidayService/` address is the URL where the web service can be invoked.
|
||||
====
|
||||
|
||||
The preceding listing shows the final WSDL. We describe how to implement the resulting schema and WSDL in the next section.
|
||||
|
||||
[[tutorial-creating-project]]
|
||||
== Creating the project
|
||||
|
||||
In this section, we use https://maven.apache.org/[Maven] to create the initial project structure for us. Doing so is not required but greatly reduces the amount of code we have to write to setup our HolidayService.
|
||||
|
||||
The following command creates a Maven web application project for us by using the Spring-WS archetype (that is, project template):
|
||||
|
||||
====
|
||||
----
|
||||
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
|
||||
-DarchetypeArtifactId=spring-ws-archetype \
|
||||
-DarchetypeVersion= \
|
||||
-DgroupId=com.mycompany.hr \
|
||||
-DartifactId=holidayService
|
||||
----
|
||||
====
|
||||
|
||||
The preceding command creates a new directory called `holidayService`. In this directory is a `src/main/webapp` directory, which contains the root of the WAR file. You can find the standard web application deployment descriptor (`'WEB-INF/web.xml'`) here, which defines a Spring-WS `MessageDispatcherServlet` and maps all incoming requests to this servlet:
|
||||
|
||||
====
|
||||
[source,xml,subs="verbatim,quotes"]
|
||||
----
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
|
||||
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
|
||||
version="2.4">
|
||||
|
||||
<display-name>MyCompany HR Holiday Service</display-name>
|
||||
|
||||
_<!-- take special notice of the name of this servlet -->_
|
||||
<servlet>
|
||||
<servlet-name>**__spring-ws__**</servlet-name>
|
||||
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring-ws</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
----
|
||||
====
|
||||
|
||||
In addition to the preceding `WEB-INF/web.xml` file, you also need another, Spring-WS-specific, configuration file, named `WEB-INF/spring-ws-servlet.xml`. This file contains all of the Spring-WS-specific beans, such as `EndPoints` and `WebServiceMessageReceivers` and is used to create a new Spring container. The name of this file is derived from the name of the attendant servlet (in this case `'spring-ws'`) with `-servlet.xml` appended to it. So if you define a `MessageDispatcherServlet` with the name `'dynamite'`, the name of the Spring-WS-specific configuration file becomes `WEB-INF/dynamite-servlet.xml`.
|
||||
|
||||
(You can see the contents of the `WEB-INF/spring-ws-servlet.xml` file for this example in <<tutorial.example.sws-conf-file>>.)
|
||||
|
||||
Once you had the project structure created, you can put the schema and the WSDL from the previous section into `'WEB-INF/'` folder.
|
||||
|
||||
[[tutorial-implementing-endpoint]]
|
||||
== Implementing the Endpoint
|
||||
|
||||
In Spring-WS, you implement endpoints to handle incoming XML messages. An endpoint is typically created by annotating a class with the `@Endpoint` annotation. In this endpoint class, you can create one or more methods that handle incoming request. The method signatures can be quite flexible. You can include almost any sort of parameter type related to the incoming XML message, as we explain later in this chapter.
|
||||
|
||||
=== Handling the XML Message
|
||||
|
||||
In this sample application, we use http://www.jdom.org/[JDom 2] to handle the XML message. We also use https://www.w3.org/TR/xpath20/[XPath], because it lets us select particular parts of the XML JDOM tree without requiring strict schema conformance.
|
||||
|
||||
The following listing shows the class that defines our holiday endpoint:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package com.mycompany.hr.ws;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.ws.server.endpoint.annotation.Endpoint;
|
||||
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
|
||||
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
|
||||
|
||||
import com.mycompany.hr.service.HumanResourceService;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.JDOMException;
|
||||
import org.jdom2.Namespace;
|
||||
import org.jdom2.filter.Filters;
|
||||
import org.jdom2.xpath.XPathExpression;
|
||||
import org.jdom2.xpath.XPathFactory;
|
||||
|
||||
@Endpoint // <1>
|
||||
public class HolidayEndpoint {
|
||||
|
||||
private static final String NAMESPACE_URI = "http://mycompany.com/hr/schemas";
|
||||
|
||||
private XPathExpression<Element> startDateExpression;
|
||||
|
||||
private XPathExpression<Element> endDateExpression;
|
||||
|
||||
private XPathExpression<Element> firstNameExpression;
|
||||
|
||||
private XPathExpression<Element> lastNameExpression;
|
||||
|
||||
private HumanResourceService humanResourceService;
|
||||
|
||||
@Autowired // <2>
|
||||
public HolidayEndpoint(HumanResourceService humanResourceService) throws JDOMException {
|
||||
this.humanResourceService = humanResourceService;
|
||||
|
||||
Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URI);
|
||||
XPathFactory xPathFactory = XPathFactory.instance();
|
||||
startDateExpression = xPathFactory.compile("//hr:StartDate", Filters.element(), null, namespace);
|
||||
endDateExpression = xPathFactory.compile("//hr:EndDate", Filters.element(), null, namespace);
|
||||
firstNameExpression = xPathFactory.compile("//hr:FirstName", Filters.element(), null, namespace);
|
||||
lastNameExpression = xPathFactory.compile("//hr:LastName", Filters.element(), null, namespace);
|
||||
}
|
||||
|
||||
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest") // <3>
|
||||
public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {// <4>
|
||||
Date startDate = parseDate(startDateExpression, holidayRequest);
|
||||
Date endDate = parseDate(endDateExpression, holidayRequest);
|
||||
String name = firstNameExpression.evaluateFirst(holidayRequest).getText() + " " + lastNameExpression.evaluateFirst(holidayRequest).getText();
|
||||
|
||||
humanResourceService.bookHoliday(startDate, endDate, name);
|
||||
}
|
||||
|
||||
private Date parseDate(XPathExpression<Element> expression, Element element) throws ParseException {
|
||||
Element result = expression.evaluateFirst(element);
|
||||
if (result != null) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
return dateFormat.parse(result.getText());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not evaluate [" + expression + "] on [" + element + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
<1> The `HolidayEndpoint` is annotated with `@Endpoint`. This marks the class as a special sort of `@Component`, suitable for handling XML messages in Spring-WS, and also makes it eligible for suitable for component scanning.
|
||||
<2> The `HolidayEndpoint` requires the `HumanResourceService` business service to operate, so we inject the dependency in the constructor and annotate it with `@Autowired`.
|
||||
Next, we set up XPath expressions by using the JDOM2 API. There are four expressions: `//hr:StartDate` for extracting the `<StartDate>` text value, `//hr:EndDate` for extracting the end date, and two for extracting the names of the employee.
|
||||
<3> The `@PayloadRoot` annotation tells Spring-WS that the `handleHolidayRequest` method is suitable for handling XML messages. The sort of message that this method can handle is indicated by the annotation values. In this case, it can
|
||||
handle XML elements that have the `HolidayRequest` local part and the `http://mycompany.com/hr/schemas` namespace.
|
||||
More information about mapping messages to endpoints is provided in the next section.
|
||||
<4> The `handleHolidayRequest(..)` method is the main handling method, which gets passed the `<HolidayRequest/>`
|
||||
element from the incoming XML message. The `@RequestPayload` annotation indicates that the `holidayRequest` parameter should be mapped to the payload of the
|
||||
request message. We use the XPath expressions to extract the string values from the XML messages and convert these values to `Date` objects by using a
|
||||
`SimpleDateFormat` (the `parseData` method). With these values, we invoke a method on the business service.
|
||||
Typically, this results in a database transaction being started and some records being altered in the database.
|
||||
Finally, we define a `void` return type, which indicates to Spring-WS that we do not want to send a response message.
|
||||
If we want a response message, we could return a JDOM Element to represent the payload of the response message.
|
||||
====
|
||||
|
||||
Using JDOM is just one of the options to handle the XML. Other options include DOM, dom4j, XOM, SAX, and StAX, but also marshalling techniques like JAXB, Castor, XMLBeans, JiBX, and XStream, as explained in <<common,the next chapter>>. We chose JDOM because it gives us access to the raw XML and because it is based on classes (not interfaces and factory methods as with W3C DOM and dom4j), which makes the code less verbose. We use XPath because it is less fragile than marshalling technologies. We do not need strict schema conformance as long as we can find the dates and the name.
|
||||
|
||||
Because we use JDOM, we must add some dependencies to the Maven `pom.xml`, which is in the root of our project directory. Here is the relevant section of the POM:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ws</groupId>
|
||||
<artifactId>spring-ws-core</artifactId>
|
||||
<version></version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jdom</groupId>
|
||||
<artifactId>jdom</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jaxen</groupId>
|
||||
<artifactId>jaxen</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
====
|
||||
|
||||
Here is how we would configure these classes in our `spring-ws-servlet.xml` Spring XML configuration file by using component scanning. We also instruct Spring-WS to use annotation-driven endpoints, with the `<sws:annotation-driven>` element.
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:sws="http://www.springframework.org/schema/web-services"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
|
||||
|
||||
<context:component-scan base-package="com.mycompany.hr"/>
|
||||
|
||||
<sws:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
=== Routing the Message to the Endpoint
|
||||
|
||||
As part of writing the endpoint, we also used the `@PayloadRoot` annotation to indicate which sort of messages can be handled by the `handleHolidayRequest` method. In Spring-WS, this process is the responsibility of an `EndpointMapping`. Here, we route messages based on their content by using a `PayloadRootAnnotationMethodEndpointMapping`. The following listing shows the annotation we used earlier:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
|
||||
----
|
||||
====
|
||||
|
||||
The annotation shown in the preceding example basically means that whenever an XML message is received with the namespace `http://mycompany.com/hr/schemas` and the `HolidayRequest` local name, it is routed to the `handleHolidayRequest` method. By using the `<sws:annotation-driven>` element in our configuration, we enable the detection of the `@PayloadRoot` annotations. It is possible (and quite common) to have multiple, related handling methods in an endpoint, each of them handling different XML messages.
|
||||
|
||||
There are also other ways to map endpoints to XML messages, which is described in <<common,the next chapter>>.
|
||||
|
||||
=== Providing the Service and Stub implementation
|
||||
|
||||
Now that we have the endpoint, we need `HumanResourceService` and its implementation for use by `HolidayEndpoint`. The following listing shows the `HumanResourceService` interface:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package com.mycompany.hr.service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface HumanResourceService {
|
||||
void bookHoliday(Date startDate, Date endDate, String name);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
For tutorial purposes, we use a simple stub implementation of the `HumanResourceService`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package com.mycompany.hr.service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service // <1>
|
||||
public class StubHumanResourceService implements HumanResourceService {
|
||||
public void bookHoliday(Date startDate, Date endDate, String name) {
|
||||
System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> The `StubHumanResourceService` is annotated with `@Service`. This marks the class as a business facade, which makes this a candidate for injection by `@Autowired` in `HolidayEndpoint`.
|
||||
====
|
||||
|
||||
[[tutorial-publishing-wsdl]]
|
||||
== Publishing the WSDL
|
||||
|
||||
Finally, we need to publish the WSDL. As stated in <<tutorial-service-contract>>, we do not need to write a WSDL ourselves. Spring-WS can generate one based on some conventions. Here is how we define the generation:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<sws:dynamic-wsdl id="holiday" <!--1-->
|
||||
portTypeName="HumanResource" <!--3-->
|
||||
locationUri="/holidayService/" <!--4-->
|
||||
targetNamespace="http://mycompany.com/hr/definitions"> <!--5-->
|
||||
<sws:xsd location="/WEB-INF/hr.xsd"/> <!--2-->
|
||||
</sws:dynamic-wsdl>
|
||||
----
|
||||
|
||||
<1> The `id` determines the URL where the WSDL can be retrieved. In this case, the `id` is `holiday`, which means that the WSDL can be retrieved
|
||||
as `holiday.wsdl` in the servlet context. The full URL is `http://localhost:8080/holidayService/holiday.wsdl`.
|
||||
<2> Next, we set the WSDL port type to be `HumanResource`.
|
||||
<3> We set the location where the service can be reached: `/holidayService/`. We use a relative URI, and we instruct the framework to transform it
|
||||
dynamically to an absolute URI. Hence, if the service is deployed to different contexts, we do not have to change the URI manually.
|
||||
For more information, see <<server-automatic-wsdl-exposure,the section called "`Automatic WSDL exposure`">>. For the location transformation to work, we need to add an init parameter to `spring-ws`
|
||||
servlet in `web.xml` (shown in the next listing).
|
||||
<4> We define the target namespace for the WSDL definition itself. Setting this attribute is not required. If not set, the WSDL has the same namespace as the XSD schema.
|
||||
<5> The `xsd` element refers to the human resource schema we defined in <<tutorial.xsd>>. We placed the schema in the `WEB-INF` directory of the application.
|
||||
====
|
||||
|
||||
The following listing shows how to add the init parameter:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<init-param>
|
||||
<param-name>transformWsdlLocations</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
----
|
||||
====
|
||||
|
||||
You can create a WAR file by using `mvn install`. If you deploy the application (to Tomcat, Jetty, and so on) and point your browser at http://localhost:8080/holidayService/holiday.wsdl[this location], you see the generated WSDL. This WSDL is ready to be used by clients, such as http://www.soapui.org/[soapUI] or other SOAP frameworks.
|
||||
|
||||
That concludes this tutorial. The tutorial code can be found in the full distribution of Spring-WS. If you wish to continue, look at the echo sample application that is part of the distribution. After that, look at the airline sample, which is a bit more complicated, because it uses JAXB, WS-Security, Hibernate, and a transactional service layer. Finally, you can read the rest of the reference documentation.
|
||||
76
spring-ws-docs/src/docs/asciidoc/what-is-spring-ws.adoc
Normal file
@@ -0,0 +1,76 @@
|
||||
[[what-is-spring-ws]]
|
||||
= What is Spring Web Services?
|
||||
|
||||
== Introduction
|
||||
|
||||
Spring Web Services (Spring-WS) is a product of the Spring community and is focused on creating document-driven web services. Spring Web Services aims to facilitate contract-first SOAP service development, allowing for the creation of flexible web services by using one of the many ways to manipulate XML payloads. The product is based on Spring itself, which means you can use the Spring concepts (such as dependency injection) as an integral part of your web service.
|
||||
|
||||
People use Spring-WS for many reasons, but most are drawn to it after finding alternative SOAP stacks lacking when it comes to following web service best practices. Spring-WS makes the best practice an easy practice. This includes practices such as the WS-I basic profile, contract-first development, and having a loose coupling between contract and implementation. The other key features of Spring Web Services are:
|
||||
|
||||
* <<features-powerful-mappings>>
|
||||
* <<features-xml-api-support>>
|
||||
* <<features-flexible-xml-marshalling>>
|
||||
* <<features-reusing-your-spring-expertise>>
|
||||
* <<features-support-for-ws-security>>
|
||||
* <<features-integration-with-spring-security>>
|
||||
* <<features-apache-license>>
|
||||
|
||||
[[features-powerful-mappings]]
|
||||
=== Powerful mappings
|
||||
|
||||
You can distribute incoming XML requests to any object, depending on message payload, SOAP Action header, or an XPath expression.
|
||||
|
||||
[[features-xml-api-support]]
|
||||
=== XML API support
|
||||
|
||||
Incoming XML messages can be handled not only with standard JAXP APIs such as DOM, SAX, and StAX, but also with JDOM, dom4j, XOM, or even marshalling technologies.
|
||||
|
||||
[[features-flexible-xml-marshalling]]
|
||||
=== Flexible XML Marshalling
|
||||
|
||||
Spring Web Services builds on the Object/XML Mapping module in the Spring Framework, which supports JAXB 1 and 2, Castor, XMLBeans, JiBX, and XStream.
|
||||
|
||||
[[features-reusing-your-spring-expertise]]
|
||||
=== Reusing Your Spring expertise
|
||||
|
||||
Spring-WS uses Spring application contexts for all configuration, which should help Spring developers get up-to-speed quickly. Also, the architecture of Spring-WS resembles that of Spring-MVC.
|
||||
|
||||
[[features-support-for-ws-security]]
|
||||
=== Support for WS-Security
|
||||
|
||||
WS-Security lets you sign SOAP messages, encrypt and decrypt them, or authenticate against them.
|
||||
|
||||
[[features-integration-with-spring-security]]
|
||||
=== Integration with Spring Security
|
||||
|
||||
The WS-Security implementation of Spring Web Services provides integration with Spring Security. This means you can use your existing Spring Security configuration for your SOAP service as well.
|
||||
|
||||
[[features-apache-license]]
|
||||
=== Apache license
|
||||
|
||||
You can confidently use Spring-WS in your project.
|
||||
|
||||
== Runtime environment
|
||||
|
||||
Spring Web Services requires a standard Java 8 Runtime Environment. Spring-WS is built on Spring Framework 4.0.9, but higher versions are supported.
|
||||
|
||||
Spring-WS consists of a number of modules, which are described in the remainder of this section.
|
||||
|
||||
* The XML module (`spring-xml.jar`) contains various XML support classes for Spring Web Services. This module is mainly intended for the Spring-WS framework itself and not web service developers.
|
||||
* The Core module (`spring-ws-core.jar`) is the central part of the Spring's web services functionality. It provides the central <<web-service-messages,`WebServiceMessage`>> and <<soap-message,`SoapMessage`>> interfaces, the <<server,server-side>> framework (with powerful message dispatching), the various support classes for implementing web service endpoints, and the <<client,client-side>> `WebServiceTemplate`.
|
||||
* The Support module (`spring-ws-support.jar`) contains additional transports (JMS, Email, and others).
|
||||
* The <<security,Security>> package (`spring-ws-security.jar`) provides a WS-Security implementation that integrates with the core web service package. It lets you sign, decrypt and encrypt, and add principal tokens to SOAP messages. Additionally, it lets you use your existing Spring Security security implementation for authentication and authorization.
|
||||
|
||||
The following figure shows and the dependencies between the Spring-WS modules. Arrows indicate dependencies (that is, Spring-WS Core depends on Spring-XML and the OXM module found in Spring 3 and higher).
|
||||
|
||||
image::images/spring-deps.png[align="center"]
|
||||
|
||||
== Supported standards
|
||||
|
||||
Spring Web Services supports the following standards:
|
||||
|
||||
* SOAP 1.1 and 1.2
|
||||
* WSDL 1.1 and 2.0 (XSD-based generation is supported only for WSDL 1.1)
|
||||
* WS-I Basic Profile 1.0, 1.1, 1.2, and 2.0
|
||||
* WS-Addressing 1.0 and the August 2004 draft
|
||||
* SOAP Message Security 1.1, Username Token Profile 1.1, X.509 Certificate Token Profile 1.1, SAML Token Profile 1.1, Kerberos Token Profile 1.1, Basic Security Profile 1.1
|
||||
192
spring-ws-docs/src/docs/asciidoc/why-contract-first.adoc
Normal file
@@ -0,0 +1,192 @@
|
||||
[[why-contract-first]]
|
||||
= Why Contract First?
|
||||
|
||||
When creating web services, there are two development styles: contract-last and contract-first. When you use a contract-last approach, you start with the Java code and let the web service contract (in WSDL -- see sidebar) be generated from that. When using contract-first, you start with the WSDL contract and use Java to implement the contract.
|
||||
|
||||
****
|
||||
*What is WSDL?*
|
||||
|
||||
WSDL stands for Web Service Description Language. A WSDL file is an XML document that describes a web service. It specifies the location of the service and the operations (or methods) the service exposes. For more information about WSDL, see the https://www.w3.org/TR/wsdl[WSDL specification].
|
||||
****
|
||||
|
||||
Spring-WS supports only the contract-first development style, and this section explains why.
|
||||
|
||||
== Object/XML Impedance Mismatch
|
||||
|
||||
Similar to the field of ORM, where we have an https://en.wikipedia.org/wiki/Object-Relational_impedance_mismatch[Object/Relational impedance mismatch], converting Java objects to XML has a similar problem. At first glance, the O/X mapping problem appears simple: Create an XML element for each Java object to convert all Java properties and fields to sub-elements or attributes. However, things are not as simple as they appear, because there is a fundamental difference between hierarchical languages, such as XML (and especially XSD), and the graph model of Java.
|
||||
|
||||
NOTE: Most of the contents in this section were inspired by <<alpine>> and <<effective-enterprise-java>>.
|
||||
|
||||
=== XSD Extensions
|
||||
|
||||
In Java, the only way to change the behavior of a class is to subclass it to add the new behavior to that subclass. In XSD, you can extend a data type by restricting it -- that is, constraining the valid values for the elements and attributes. For instance, consider the following example:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<simpleType name="AirportCode">
|
||||
<restriction base="string">
|
||||
<pattern value="[A-Z][A-Z][A-Z]"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
----
|
||||
|
||||
This type restricts a XSD string by way of a regular expression, allowing only three upper case letters. If this type is converted to Java, we end up with an ordinary `java.lang.String`. The regular expression is lost in the conversion process, because Java does not allow for these sorts of extensions.
|
||||
|
||||
=== Unportable Types
|
||||
|
||||
One of the most important goals of a web service is to be interoperable: to support multiple platforms such as Java, .NET, Python, and others. Because all of these languages have different class libraries, you must use some common, cross-language format to communicate between them. That format is XML, which is supported by all of these languages.
|
||||
|
||||
Because of this conversion, you must make sure that you use portable types in your service implementation. Consider, for example, a service that returns a `java.util.TreeMap`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public Map getFlights() {
|
||||
// use a tree map, to make sure it's sorted
|
||||
TreeMap map = new TreeMap();
|
||||
map.put("KL1117", "Stockholm");
|
||||
...
|
||||
return map;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Undoubtedly, the contents of this map can be converted into some sort of XML, but since there is no standard way to describe a map in XML, it will be proprietary. Also, even if it can be converted to XML, many platforms do not have a data structure similar to the `TreeMap`. So when a .NET client accesses your web service, it probably ends up with a `System.Collections.Hashtable`, which has different semantics.
|
||||
|
||||
This problem is also present when working on the client side. Consider the following XSD snippet, which describes a service contract:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<element name="GetFlightsRequest">
|
||||
<complexType>
|
||||
<all>
|
||||
<element name="departureDate" type="date"/>
|
||||
<element name="from" type="string"/>
|
||||
<element name="to" type="string"/>
|
||||
</all>
|
||||
</complexType>
|
||||
</element>
|
||||
----
|
||||
====
|
||||
|
||||
This contract defines a request that takes an `date`, which is a XSD datatype representing a year, month, and day. If we call this service from Java, we probably use either a `java.util.Date` or `java.util.Calendar`. However, both of these classes actually describe times, rather than dates. So, we actually end up sending data that represents the fourth of April 2007 at midnight (`2007-04-04T00:00:00`), which is not the same as `2007-04-04`.
|
||||
|
||||
=== Cyclic Graphs
|
||||
|
||||
Imagine we have the following class structure:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Flight {
|
||||
private String number;
|
||||
private List<Passenger> passengers;
|
||||
|
||||
// getters and setters omitted
|
||||
}
|
||||
|
||||
public class Passenger {
|
||||
private String name;
|
||||
private Flight flight;
|
||||
|
||||
// getters and setters omitted
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This is a cyclic graph: the `Flight` refers to the `Passenger`, which refers to the `Flight` again. Cyclic graphs like these are quite common in Java. If we take a naive approach to converting this to XML, we end up with something like:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<flight number="KL1117">
|
||||
<passengers>
|
||||
<passenger>
|
||||
<name>Arjen Poutsma</name>
|
||||
<flight number="KL1117">
|
||||
<passengers>
|
||||
<passenger>
|
||||
<name>Arjen Poutsma</name>
|
||||
<flight number="KL1117">
|
||||
<passengers>
|
||||
<passenger>
|
||||
<name>Arjen Poutsma</name>
|
||||
...
|
||||
----
|
||||
====
|
||||
|
||||
Processing such a structure is likely to take a long time to finish, because there is no stop condition for this loop.
|
||||
|
||||
One way to solve this problem is to use references to objects that were already marshalled:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<flight number="KL1117">
|
||||
<passengers>
|
||||
<passenger>
|
||||
<name>Arjen Poutsma</name>
|
||||
<flight href="KL1117" />
|
||||
</passenger>
|
||||
...
|
||||
</passengers>
|
||||
</flight>
|
||||
----
|
||||
====
|
||||
|
||||
This solves the recursion problem but introduces new ones. For one, you cannot use an XML validator to validate this structure. Another issue is that the standard way to use these references in SOAP (RPC/encoded) has been deprecated in favor of document/literal (see the WS-I http://www.ws-i.org/Profiles/BasicProfile-1.1.html#SOAP_encodingStyle_Attribute[Basic Profile]).
|
||||
|
||||
These are just a few of the problems when dealing with O/X mapping. It is important to respect these issues when writing web services. The best way to respect them is to focus on the XML completely, while using Java as an implementation language. This is what contract-first is all about.
|
||||
|
||||
== Contract-first Versus Contract-last
|
||||
|
||||
Besides the Object/XML Mapping issues mentioned in the previous section, there are other reasons for preferring a contract-first development style.
|
||||
|
||||
* <<contract-first-fragility>>
|
||||
* <<contract-first-performance>>
|
||||
* <<contract-first-reusability>>
|
||||
* <<contract-first-versioning>>
|
||||
|
||||
[[contract-first-fragility]]
|
||||
=== Fragility
|
||||
|
||||
As mentioned earlier, the contract-last development style results in your web service contract (WSDL and your XSD) being generated from your Java contract (usually an interface). If you use this approach, you have no guarantee that the contract stays constant over time. Each time you change your Java contract and redeploy it, there might be subsequent changes to the web service contract.
|
||||
|
||||
Additionally, not all SOAP stacks generate the same web service contract from a Java contract. This means that changing your current SOAP stack for a different one (for whatever reason) might also change your web service contract.
|
||||
|
||||
When a web service contract changes, users of the contract have to be instructed to obtain the new contract and potentially change their code to accommodate for any changes in the contract.
|
||||
|
||||
For a contract to be useful, it must remain constant for as long as possible. If a contract changes, you have to contact all the users of your service and instruct them to get the new version of the contract.
|
||||
|
||||
[[contract-first-performance]]
|
||||
=== Performance
|
||||
|
||||
When a Java object is automatically transformed into XML, there is no way to be sure as to what is sent across the wire. An object might reference another object, which refers to another, and so on. In the end, half of the objects on the heap in your virtual machine might be converted into XML, which results in slow response times.
|
||||
|
||||
When using contract-first, you explicitly describe what XML is sent where, thus making sure that it is exactly what you want.
|
||||
|
||||
[[contract-first-reusability]]
|
||||
=== Reusability
|
||||
|
||||
Defining your schema in a separate file lets you reuse that file in different scenarios. Consider the definition of an `AirportCode` in a file called `airline.xsd`:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<simpleType name="AirportCode">
|
||||
<restriction base="string">
|
||||
<pattern value="[A-Z][A-Z][A-Z]"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
----
|
||||
====
|
||||
|
||||
You can reuse this definition in other schemas, or even WSDL files, by using an `import` statement.
|
||||
|
||||
[[contract-first-versioning]]
|
||||
=== Versioning
|
||||
|
||||
Even though a contract must remain constant for as long as possible, they do need to be changed sometimes. In Java, this typically results in a new Java interface, such as `AirlineService2`, and a (new) implementation of that interface. Of course, the old service must be kept around, because there might be clients who have not yet migrated.
|
||||
|
||||
If using contract-first, we can have a looser coupling between contract and implementation. Such a looser coupling lets us implement both versions of the contract in one class. We could, for instance, use an XSLT stylesheet to convert any "`old-style`" messages to the "`new-style`" messages.
|
||||