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 message dispatching framework, a WS-Security solution that integrates with your existing application security solution, and a Client-side API that follows the familiar Spring template pattern.
I. Introduction
1. What is Spring Web Services?
1.1. 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:
1.1.1. Powerful mappings
You can distribute incoming XML requests to any object, depending on message payload, SOAP Action header, or an XPath expression.
1.1.2. 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.
1.1.3. 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.
1.1.4. 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.
1.1.5. Support for WS-Security
WS-Security lets you sign SOAP messages, encrypt and decrypt them, or authenticate against them.
1.2. 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 centralWebServiceMessage
andSoapMessage
interfaces, the server-side framework (with powerful message dispatching), the various support classes for implementing web service endpoints, and the client-sideWebServiceTemplate
. -
The Support module (
spring-ws-support.jar
) contains additional transports (JMS, Email, and others). -
The 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).
1.3. 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
2. 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.
Spring-WS supports only the contract-first development style, and this section explains why.
2.1. Object/XML Impedance Mismatch
Similar to the field of ORM, where we have an 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.
Most of the contents in this section were inspired by [alpine] and [effective-enterprise-java]. |
2.1.1. 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:
<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.
2.1.2. 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
:
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:
<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
.
2.1.3. Cyclic Graphs
Imagine we have the following class structure:
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:
<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:
<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 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.
2.2. 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.
2.2.1. 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.
2.2.2. 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.
2.2.3. 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
:
<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.
2.2.4. 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.
3. Writing Contract-First Web Services
This tutorial shows you how to write 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 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.
3.1. 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.
3.1.1. Holiday
In the scenario, we have to deal with holiday requests, so it makes sense to determine what a holiday looks like in 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 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.
3.1.2. Employee
There is also the notion of an employee in the scenario. Here is what it looks like in 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
.
3.1.3. HolidayRequest
Both the holiday
element and the employee
element can be put in a <HolidayRequest/>
:
<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.
3.2. 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 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:
<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:
<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:
<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:
<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
.
3.3. Service Contract
A service contract is generally expressed as a 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 Implementing the Endpoint. The remainder of this section shows how to write WSDL by hand. You may want to skip to 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:
<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:
<wsdl:message name="HolidayRequest">
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
</wsdl:message>
We add the message to a port type as an operation:
<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
):
<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 Data Contract. |
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.
3.4. Creating the project
In this section, we use 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:
<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.
3.5. 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.
3.5.1. Handling the XML Message
In this sample application, we use JDom 2 to handle the XML message. We also use 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:
package com.mycompany.hr.ws;
@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 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:
<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.
<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>
3.5.2. 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:
@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 the next chapter.
3.5.3. 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:
package com.mycompany.hr.service;
public interface HumanResourceService {
void bookHoliday(Date startDate, Date endDate, String name);
}
For tutorial purposes, we use a simple stub implementation of the HumanResourceService
:
package com.mycompany.hr.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 . |
3.6. Publishing the WSDL
Finally, we need to publish the WSDL. As stated in 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:
<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 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 Data Contract. We placed the schema in the WEB-INF directory of the application. |
The following listing shows how to add the init parameter:
<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 this location, you see the generated WSDL. This WSDL is ready to be used by clients, such as 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.