In this tutorial, we will 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.
The most important thing when doing contract-first Web service development is to try and think 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. The fact that Java is used to implement the Web service is an implementation detail. An important detail, but a detail nonetheless.
In this section, we will focus on the actual XML messages that are sent to and from the service. We will start out by determining what these messages look like.
In the scenario, we have to deal with holiday requests, so it makes sense to determine what a holiday looks like:
<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 decided to use the standard ISO 8601 date format for the dates, because that will save a lot of parsing hassle. We also added a namespace to the element, to make sure our elements can used within other XML documents.
There is also the notion of an employee in the scenario. Here's what it looks like:
<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://mycompany.com/employees/schemas
.
Both the holiday and 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 element does not matter: Employee
could have been the first
element just as well. As long as all the data is there; that's what is important. In fact, the data
is the only thing that is important: we are taking a data-driven approach.
Now that we have seen some examples of the XML data that we will use, it makes sense to formalize this into a schema. This data contract defines the message format we accept. Basically, there are four different ways of defining such a contract for XML:
DTDs have limited namespaces support, so they are not suitable for Web services. Relax NG and Schematron are certainly easier than XSDs. Unfortunately, they are not so widely supported across platforms. We will use XML Schema.
By far the easiest way to create a 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, and generate a schema from it that validates them all. The end result certainly needs to be polished up, but it's a great starting point.
Using the sample described above, 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>
The generated schema can obviously 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 only want to accept a HolidayRequest
. By removing the wrapping element tags
(thus keeping the types), and inlining the results, we can accomplish this.
<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 messages to validate:
<HolidayRequest xmlns="http://mycompany.com/hr/schemas"> <Holiday> <StartDate>this is not a date</StartDate> <EndDate>neither is this</EndDate> </Holiday> ... </HolidayRequest>
Clearly, we must make sure that the start and end date are really dates. XML Schema has an excellent built-in
date
type which we can use. We also change the NCName
s to
string
s. 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 looks like this:
<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"/> <xs:element name="Employee" type="hr:EmployeeType"/> </xs:all> </xs:complexType> </xs:element> <xs:complexType name="HolidayType"> <xs:sequence> <xs:element name="StartDate" type="xs:date"/>
<xs:element name="EndDate" type="xs:date"/>
</xs:sequence> </xs:complexType> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="Number" type="xs:integer"/> <xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
</xs:sequence> </xs:complexType> </xs:schema>
![]() |
|
![]() |
We use the |
![]() |
|
We store this file with as hr.xsd
.
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 Section 3.6, “Implementing the Endpoint”. You can skip to that section if you want to; the remainder of this section will show you how to write your own WSDL by hand.
We start our WSDL with the standard preamble, and by importing our existing XSD. To
separate the schema from the definition, we will use a separate namespace for the WSDL definitions:
http://mycompany.com/hr/definitions
.
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/hr/definitions"
xmlns:tns="http://mycompany.com/hr/definitions"
xmlns:types="http://mycompany.com/hr/schemas">
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<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>
</wsdl:definitions>
Next, we define our messages based on the written schema. We only have one message: one with the
HolidayRequest
we put in the schema:
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/hr/definitions"
xmlns:tns="http://mycompany.com/hr/definitions"
xmlns:types="http://mycompany.com/hr/schemas"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<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>
<wsdl:message name="RequestHolidayInput">>
<wsdl:part name="body" element="types:HolidayRequest" />
</wsdl:message>
</wsdl:definitions>
We add the message to a port type as operation:
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/hr/definitions"
xmlns:tns="http://mycompany.com/hr/definitions"
xmlns:types="http://mycompany.com/hr/schemas"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<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>
<wsdl:message name="RequestHolidayInput">
<wsdl:part name="body" element="types:HolidayRequest" />
</wsdl:message>
<wsdl:portType name="HumanResourcesPortType">
<wsdl:operation name="RequestHoliday">
<wsdl:input message="tns:RequestHolidayInput" />
</wsdl:operation>
</wsdl:portType>
</wsdl:definitions>
That finished 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've just defined; and a service
, which tells it
where to invoke it.
Adding a concrete part is pretty standard: just 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://example.com/RequestHoliday
, but any URI will do), and determine the
location
URL where you want request to come in (in this case
http://mycompany.com/humanresources
):
<wsdl:definitions name="HumanResources" targetNamespace="http://mycompany.com/hr/definitions" xmlns:tns="http://mycompany.com/hr/definitions" xmlns:types="http://mycompany.com/hr/schemas" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <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> <wsdl:message name="RequestHolidayInput">
<wsdl:part name="body" element="types:HolidayRequest" />
</wsdl:message> <wsdl:portType name="HumanResourcesPortType">
<wsdl:operation name="RequestHoliday"> <wsdl:input message="tns:RequestHolidayInput" />
</wsdl:operation> </wsdl:portType> <wsdl:binding name="HumanResourcesBinding" type="tns:HumanResourcesPortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="RequestHoliday"> <soap:operation soapAction="http://example.com/RequestHoliday" />
<wsdl:input> <soap:body use="literal" />
</wsdl:input> </wsdl:operation> </wsdl:binding> <wsdl:service name="HumanResourcesService"> <wsdl:port name="HumanResourcesPort" binding="tns:HumanResourcesBinding">
<soap:address location="http://mycompany.com/humanresources" />
</wsdl:port> </wsdl:service> </wsdl:definitions>
![]() | We import the schema defined in Section 3.3, “Data Constract”. |
![]() |
We define the |
![]() |
The |
![]() |
We define the |
![]() |
We define the |
![]() | We use a document/literal style. |
![]() |
The literal |
![]() |
The |
![]() |
The |
This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the next section.
In this section, we will be using Maven2 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 Maven2 web application project for us, using the Spring-WS archetype (i.e. project template)[2]
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \ -DarchetypeArtifactId=spring-ws-archetype \ -DarchetypeVersion=1.0-rc1-SNAPSHOT \ -DgroupId=com.mycompany.hr \ -DartifactId=holidayService
This command will create a new directory called holidayService
. In this directory,
there is a src/main/webapp
directory, which will contain the root of the WAR file.
You will 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>
<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>
We could have made the servlet more restrictive by using the url pattern /humanresources
,
but this will suffice for now.
Additionally, there is WEB-INF/spring-ws-servlet.xml
, which is a Spring application
context that will contain the Spring-WS bean definitions.
In Spring-WS, you will implement Endpoints to handle incoming XML messages. There are two flavors of endpoints: message endpoints and payload endpoints.. Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, the endpoint will only be interested in the payload of the message, i.e. the contents of the SOAP body. In that case, creating a payload endpoint makes more sense.
In this sample application, we are going to use JDom to handle the
XML message. We are also using XPath, because it
allows us to select particular parts of the XML JDOM tree, without requiring strict schema conformance.
We extend our endpoint from AbstractJDomPayloadEndpoint
,
because that will give us a JDOM element to execute the XPath queries on.
package com.mycompany.hr.ws; import java.text.SimpleDateFormat; import java.util.Date; import com.mycompany.hr.service.HumanResourceService; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jdom.xpath.XPath; import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint; public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { private XPath startDateExpression; private XPath endDateExpression; private XPath nameExpression; private HumanResourceService humanResourceService; public HolidayEndpoint(HumanResourceService humanResourceService) {this.humanResourceService = humanResourceService; } public void init() throws JDOMException {
Namespace namespace = Namespace.getNamespace("hr", "http://mycompany.com/hr/schemas"); startDateExpression = XPath.newInstance("//hr:StartDate"); startDateExpression.addNamespace(namespace); endDateExpression = XPath.newInstance("//hr:EndDate"); endDateExpression.addNamespace(namespace); nameExpression = XPath.newInstance("//hr:FirstName|//hr:LastName"); nameExpression.addNamespace(namespace); } protected Element invokeInternal(Element holidayRequest) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date startDate = dateFormat.parse(startDateExpression.valueOf(holidayRequest)); Date endDate = dateFormat.parse(endDateExpression.valueOf(holidayRequest)); String name = nameExpression.valueOf(holidayRequest); humanResourceService.bookHoliday(startDate, endDate, name); return null; } }
![]() |
The |
![]() |
The initialization method |
![]() |
The |
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. 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 don't care for strict schema conformance, as long as we can find the dates and the name.
Here's how we would wire up these classes in our spring-ws-servlet.xml
application context:
<beans xmlns="http://www.springframework.org/schema/beans"> <bean id="holidayEndpoint" class="com.mycompany.hr.ws.HolidayEndpoint" init-method="init"> <constructor-arg ref="hrService"/> </bean> <bean id="hrService" class="com.mycompany.hr.service.StubHumanResourceService"/> </beans>
Now that we have written an endpoint that handles the message, we must define how incoming messages
are routed to that endpoint. In Spring-WS, this is the responsibility of an
EndpointMapping
. In this tutorial, we will route messages based on
their content, by using a PayloadRootQNameEndpointMapping
. Here's how we
wire it up in spring-ws-servlet.xml
:
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="mappings"> <props> <prop key="{http://mycompany.com/hr/schemas}HolidayRequest">holidayEndpoint</prop> </props> </property> <property name="interceptors"> <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/> </property> </bean>
This means that whenever a XML message comes in with the namespace
http://mycompany.com/hr/schemas
and the
HolidayRequest
local name, it will be routed to the
holidayEndpoint
.
It also adds a PayloadInterceptor
,
which dumps incoming and outgoing messages to the log.
Finally, we need to publish the WSDL. As stated in Section 3.4, “Service contract”, we don't need to write a WSDL ourselves; Spring-WS can generate one for us based on some conventions. Here's how we define the generation:
<bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition"><property name="builder"> <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder"> <property name="schema" value="/WEB-INF/hr.xsd"/>
<property name="portTypeName" value="HumanResource"/>
<property name="locationUri" value="http://localhost:8080/holidayService/"/>
</bean> </property> </bean>
![]() |
The bean id determines the URL where the WSDL can be retrieved. In this case, the bean id is
|
![]() |
The |
![]() |
Next, we define the WSDL port type to be |
![]() |
Finally, we set the location where the service can be reached:
|
You can create a WAR file using mvn install. If you deploy the application, and point your browser at this location, you will 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 next step would be to 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.
[2]
Until version RC1 of Spring-WS is released, the following has to be added to to
~/.m2/settings.xml
in order to find the archetype:
<repository> <id>springframework.org</id> <name>Springframework Maven SNAPSHOT Repository</name> <url>http://static.springframework.org/maven2-snapshots/</url> <snapshots> <enabled>true</enabled> </snapshots> </repository>