Minor doc updates
This commit is contained in:
@@ -483,7 +483,20 @@
|
||||
</para>
|
||||
<para>
|
||||
The following command creates a Maven2 web application project for us, using the Spring-WS archetype
|
||||
(i.e. project template)
|
||||
(i.e. project template)<footnote>
|
||||
<para>
|
||||
Until version RC1 of Spring-WS is released, the following has to be added to to
|
||||
<filename>~/.m2/settings.xml</filename> in order to find the archetype:
|
||||
<programlisting><![CDATA[
|
||||
<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>]]></programlisting>
|
||||
</para></footnote>
|
||||
</para>
|
||||
<screen>mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
|
||||
-DarchetypeArtifactId=spring-ws-archetype \
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
-----------------------------------
|
||||
Writing Contract-first Web Services
|
||||
-----------------------------------
|
||||
|
||||
Introduction
|
||||
|
||||
This is the first part of an overall tutorial on how to approach Web services development in contract-first style,
|
||||
i.e. starting with the XML Schema/WSDL contract instead of Java code. Spring Web Services focuses on this
|
||||
development style, and this tutorial helps you get started. Note that this page contains almost no Spring-WS
|
||||
specific information: it is mostly about XML, XSD, and WSDL. The {{{tutorial2.html}second page}} focusses on
|
||||
implementing this contract using Spring-WS.
|
||||
|
||||
In this tutorial, we will define a Web service that can be used for Human Resources. Clients can send
|
||||
holiday request forms to this service to book a holiday. It is based on a metaphor for Service Oriented Architectures
|
||||
originally thought of by {{{http://blog.springframework.com/arjen/archives/2006/02/06/what-is-so-hard-about-soa/}Dan
|
||||
North}}.
|
||||
|
||||
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.
|
||||
|
||||
The Messages
|
||||
|
||||
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.
|
||||
|
||||
* Holiday
|
||||
|
||||
In the scenario, we have to deal with holiday request, 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
|
||||
{{{http://www.cl.cam.ac.uk/~mgk25/iso-time.html}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.
|
||||
|
||||
* Employee
|
||||
|
||||
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">>>.
|
||||
|
||||
* HolidayRequest
|
||||
|
||||
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.
|
||||
|
||||
The Schema
|
||||
|
||||
Now that we have seen some examples of the XML data that we will use, it makes sense to formalize this into
|
||||
a schema. Basically, there are four different ways of defining a grammar for XML:
|
||||
|
||||
* DTDs
|
||||
|
||||
* {{{http://www.w3.org/XML/Schema}XML Schema (XSD)}}
|
||||
|
||||
* {{{http://www.relaxng.org/}RELAX NG}}
|
||||
|
||||
* {{{http://www.schematron.com/}Schematron}}
|
||||
|
||||
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 everything is a root-level
|
||||
element. 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 can store this file with a convenient name such as <<<hr.xsd>>>.
|
||||
|
||||
The WSDL
|
||||
|
||||
Which leaves the WSDL. 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 {{{tutorial2.html}next section}} of
|
||||
this tutorial. The rest of this page will show you how to write your own WSDL, if you choose not to use this
|
||||
functionality.
|
||||
|
||||
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 messages to a port type as operations:
|
||||
|
||||
+-------------------------------------
|
||||
<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. This
|
||||
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 (anything else is not interoperable), pick a
|
||||
<<<soapAction>>> (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>
|
||||
+-------------------------------------
|
||||
|
||||
This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the {{{tutorial2.html}next section}}.
|
||||
@@ -1,212 +0,0 @@
|
||||
-------------------------------------------------------
|
||||
Implementing Contract-first Web Services with Spring-WS
|
||||
-------------------------------------------------------
|
||||
|
||||
|
||||
Introduction
|
||||
|
||||
This is the second part of a tutorial on how to write contract-first Web services. The first part can be found
|
||||
{{{tutorial1.html}here}}. This second part focusses on implementing the Web service contract using Spring Web Services.
|
||||
|
||||
|
||||
Setup
|
||||
|
||||
In this tutorial, 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):
|
||||
|
||||
+-------------------------------------
|
||||
> 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 project, there is a
|
||||
<<<src/main/webapp>>> directory, which will contain the root of the WAR file. In this directory, you will find
|
||||
the standard web application deployment descriptor <<<WEB-INF/web.xml>>>, which basically defines a Spring-WS
|
||||
<<<MessageDispatcherServlet>>>, and maps all incoming requests to this servlet:
|
||||
|
||||
+-------------------------------------
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
+-------------------------------------
|
||||
|
||||
|
||||
Next to this file, there is <<<WEB-INF/spring-ws-servlet.xml>>>, which is a Spring application context file that
|
||||
will contain the Spring-WS bean definitions.
|
||||
|
||||
Implementing the Endpoint
|
||||
|
||||
In Spring-WS, you will implement <<Endpoints>> to handle incoming XML messages. There are two flavors of endpoints:
|
||||
{{{http://static.springframework.org/spring-ws/site/apidocs/org/springframework/ws/server/endpoint/MessageEndpoint.html}<<<MessageEndpoints>>>}} and
|
||||
{{{http://static.springframework.org/spring-ws/site/apidocs/org/springframework/ws/server/endpoint/PayloadEndpoint.html}<<<PayloadEndpoints>>>}}.
|
||||
Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, however, 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.
|
||||
|
||||
* Handling the XML Message
|
||||
|
||||
In this sample application, we are going to use {{{http://www.jdom.org}JDom}} to handle XML message. We are also
|
||||
using {{{http://www.w3schools.com/xpath/}XPath}}, because it allows us to select particular parts of the XML JDOM tree,
|
||||
without requiring strict schema conformance. We extend
|
||||
our endpoint from
|
||||
{{{http://static.springframework.org/spring-ws/site/apidocs/org/springframework/ws/server/endpoint/AbstractJDomPayloadEndpoint.html}<<<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;
|
||||
}
|
||||
}
|
||||
+-------------------------------------
|
||||
|
||||
Let's go over the class one method at a time. The HolidayEndpoint requires the HumanResourceService business service
|
||||
to operate, so we use the constructor to inject it. Next, we have the initialization method <<<init>>>, which sets up
|
||||
the XPath expressions using the JDOM API. There are three expressions: <<<//hr:StartDate>>> for extracting the
|
||||
<<<\<StartDate\>>>> text value, <<<//hr:EndDate>>> for extracting the end date, and <<<//hr:FirstName|//hr:LastName>>>
|
||||
for extracting the name of the employee.
|
||||
|
||||
The <<<invokeInternal()>>> method is a template method, which gets passed with the <<<HolidayRequest>>> element
|
||||
from the incoming XML message. Next, we use the XPath expressions to extract the String values from the XML messages,
|
||||
and convert these values to <<<Dates>>> using a <<<SimpleDateFormat>>>. With these values, we invoke a method on the
|
||||
business service. Typically, this will result in result in a database transaction being started, and some records being
|
||||
altered in the database. Finally, we return <<<null>>>, which indicates to Spring-WS that we don't want to send a
|
||||
response message. If we wanted a response message, we could have returned a JDOM Element that represents 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. Refer to the airline sample to see how
|
||||
these are used.
|
||||
|
||||
Here's how we would wire up these classes in our <<<spring-ws-servlet.xml>>> application context:
|
||||
|
||||
+-------------------------------------
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>
|
||||
+-------------------------------------
|
||||
|
||||
* Routing the Message to the Endpoint
|
||||
|
||||
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.
|
||||
|
||||
Publishing the WSDL
|
||||
|
||||
Finally, we need to publish the WSDL. As stated on the {{{tutorial1.html}previous page}}, 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 it:
|
||||
|
||||
+-------------------------------------
|
||||
<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 first property we set is the human resource schema we defined on the {{{tutorial1.html}first page}} of this
|
||||
tutorial, <<<hr.xsd>>>: we simply placed the schema in the <<<WEB-INF>>> directory of the application. Next, we define
|
||||
the WSDL port type to be <<<HumanResource>>>. Finally, we set the location where the service can be reached:
|
||||
<<<http://localhost:8080/holidayService>>>.
|
||||
|
||||
If you deploy the application, and point your browser at
|
||||
{{{http://localhost:8080/holidayService/holiday.wsdl}<<<http://localhost:8080/holidayService/holiday.wsdl>>>}}, you will
|
||||
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 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, refer to the {{{http://static.springframework.org/spring-ws/docs/1.0-m3/reference/html/index.html}reference documentation}}.
|
||||
@@ -1,183 +0,0 @@
|
||||
-------------------
|
||||
Why Contract-First?
|
||||
-------------------
|
||||
|
||||
Why Contract-First?
|
||||
|
||||
When creating Web services, there are two development styles: <contract-last> and <contract-first>. When using a
|
||||
contract-last approach, you start with the Java code, and let the Web service contract (WSDL, see sidebar) be generated
|
||||
from that. When using contract-first, you start with the WSDL contract, and use Java to implement said contract.
|
||||
|
||||
Spring-WS only supports the contract-first development style. This page explains why.
|
||||
|
||||
* Object/XML Impedance Mismatch
|
||||
|
||||
Similar to the field of ORM, where we have an
|
||||
{{{http://en.wikipedia.org/wiki/Object-Relational_impedance_mismatch}Object/Relational impedance mismatch}}, there is a
|
||||
similar problem when converting Java objects to XML. At first glance, the O/X mapping problem appears simple: create an
|
||||
XML element for each Java object, converting all Java properties and fields to sub-elements or attributes. However,
|
||||
things are not so simple as they appear: there is a fundamental difference between hierarchical languages such as XML
|
||||
(especially XSD) and the graph model of Java. Note that most of the contents in this section was inspired by
|
||||
{{{http://www.hpl.hp.com/techreports/2005/HPL-2005-83.pdf}Rethinking the Java SOAP Stack}} and
|
||||
{{{http://safari.awprofessional.com/0321130006}Effective Enterprise Java}}.
|
||||
|
||||
** XSD extensions
|
||||
|
||||
In Java, the only way to change the behavior of a class is to subclass it, adding the new behavior to that subclass.
|
||||
In XSD, you can extend a data type by restricting it: i.e. 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 ways of a regular expression, allowing only three upper case letters. If this
|
||||
type is converted to Java, we will 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, etc. Because all of these languages have different class libraries, you must use some common, interlingual
|
||||
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>>>, like so:
|
||||
|
||||
+--------------------------------------
|
||||
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 will
|
||||
probably end 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 will probably use a <<<java.util.Data>>> or
|
||||
<<<java.util.Calendar>>>. However, both of these classes actually describe times, rather than dates. So, we will
|
||||
actually send data that represents the fourth of April 2007 at midnight (<<<2007-04-04T00:00:00>>>), which is not
|
||||
the same as the fourth of April 2007 (<<<2007-04-04>>>).
|
||||
|
||||
** Cyclic graphs
|
||||
|
||||
Imagine we have the following simple 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 took a naive approach to converting this to XML, we will 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>
|
||||
...
|
||||
+--------------------------------------
|
||||
|
||||
which will take a pretty 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, like so:
|
||||
|
||||
+--------------------------------------
|
||||
<flight number="KL1117">
|
||||
<passengers>
|
||||
<passenger>
|
||||
<name>Arjen Poutsma</name>
|
||||
<flight href="KL1117" />
|
||||
</passenger>
|
||||
...
|
||||
</passengers>
|
||||
</flight>
|
||||
+--------------------------------------
|
||||
|
||||
This solves the recursiveness 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 the SOAP (RPC/encoded) has been
|
||||
deprecated in favor of document/literal.
|
||||
|
||||
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.
|
||||
|
||||
** Fragility
|
||||
|
||||
If you use a contract-last development style, you will have no guarantee that the contract stays constant over time.
|
||||
Each redeployment of the service can possibly result in a different contract. Additionally, an upgrade of the SOAP stack
|
||||
used, or a migration to a different SOAP stack can also change said contract.
|
||||
|
||||
In order for a contract to be useful, it must remain constant for as long as possible. If a contract changes, you
|
||||
will have to contact all of the users of your service, and instruct them to get the new version of the contract.
|
||||
|
||||
** Performance
|
||||
|
||||
When Java 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, etc. In the end, half of your virtual machine
|
||||
might be converted into XML, which will result in a slow service.
|
||||
|
||||
When using contract-first, you explicitly describe what XML is sent where, thus making sure that it is exactly what
|
||||
you want.
|
||||
|
||||
** Versioning
|
||||
|
||||
Even though a contract must remain constant for as long as possible, they <do> need to be changed sometimes.
|
||||
In Java, this typically result 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 migrated
|
||||
yet.
|
||||
|
||||
If using contract-first, we can have a looser coupling between contract and implementation. Such a looser coupling
|
||||
allows us to implement both versions of the contract in one class. We could, for instance, use an XSLT to convert any
|
||||
"old-style" messages to the "new-style" messages.
|
||||
@@ -23,9 +23,8 @@
|
||||
<item name="API" href="apidocs/index.html"/>
|
||||
<item name="Reference" href="reference.html"/>
|
||||
<item name="FAQ" href="faq.html"/>
|
||||
<item name="Why Contract-First?" href="why-contract-first.html"/>
|
||||
<item name="Upgrading" href="upgrading.html"/>
|
||||
<item name="Tutorial" href="tutorial/tutorial1.html"/>
|
||||
<item name="Tutorial" href="reference/html/tutorial.html"/>
|
||||
<item name="Resources & Tools" href="resources.html"/>
|
||||
</menu>
|
||||
<menu name="Support">
|
||||
|
||||
Reference in New Issue
Block a user