Finished tutorial

This commit is contained in:
Arjen Poutsma
2007-03-15 21:42:38 +00:00
parent f822a0bbb6
commit 622a18f7d6
2 changed files with 274 additions and 54 deletions

View File

@@ -4,9 +4,11 @@
Introduction
This is 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 focusses on this development style, and this
tutorial helps you get started. Note that this chapter contains almost no Spring-WS specific information: it is mostly
about XML, XSD, and WSDL.
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 focusses 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
@@ -15,7 +17,8 @@ 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.
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
@@ -27,7 +30,7 @@ start out by determining what these messages look like.
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/holidays/schemas">
<Holiday xmlns="http://mycompany.com/hr/schemas">
<StartDate>2006-07-03</StartDate>
<EndDate>2006-07-07</EndDate>
</Holiday>
@@ -42,7 +45,7 @@ parsing hassle. We also added a namespace to the element, to make sure our eleme
There is also the notion of an employee in the scenario. Here's what it looks like:
+-------------------------------------
<Employee xmlns="http://mycompany.com/holidays/schemas">
<Employee xmlns="http://mycompany.com/hr/schemas">
<Number>42</Number>
<FirstName>Arjen</FirstName>
<LastName>Poutsma</LastName>
@@ -58,7 +61,7 @@ sense to use a different namespace, such as <<<"http://mycompany.com/employees/s
Both the holiday and employee element can be put in a <<<HolidayRequest>>>:
+-------------------------------------
<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
<Holiday>
<StartDate>2006-07-03</StartDate>
<EndDate>2006-07-07</EndDate>
@@ -73,7 +76,7 @@ sense to use a different namespace, such as <<<"http://mycompany.com/employees/s
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.
important: we are taking a <<data-driven>> approach.
The Schema
@@ -102,21 +105,21 @@ point.
+-------------------------------------
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/holidays/schemas"
xmlns:holidays="http://mycompany.com/holidays/schemas">
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="holidays:Holiday"/>
<xs:element ref="holidays:Employee"/>
<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="holidays:StartDate"/>
<xs:element ref="holidays:EndDate"/>
<xs:element ref="hr:StartDate"/>
<xs:element ref="hr:EndDate"/>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -125,9 +128,9 @@ point.
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element ref="holidays:Number"/>
<xs:element ref="holidays:FirstName"/>
<xs:element ref="holidays:LastName"/>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -144,14 +147,14 @@ types), and inlining the results, we can accomplish this.
+-------------------------------------
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:holidays="http://mycompany.com/holidays/schemas"
xmlns:hr="http://mycompany.com/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/holidays/schemas">
targetNamespace="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="Holiday" <type="holidays:HolidayType">/>
<xs:element name="Employee" <type="holidays:EmployeeType">/>
<xs:element name="Holiday" type="hr:HolidayType"/>
<xs:element name="Employee" type="hr:EmployeeType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -175,7 +178,7 @@ types), and inlining the results, we can accomplish this.
validate:
+-------------------------------------
<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
<Holiday>
<StartDate>this is not a date</StartDate>
<EndDate>neither is this</EndDate>
@@ -191,52 +194,57 @@ validate:
+-------------------------------------
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:holidays="http://mycompany.com/holidays/schemas"
xmlns:hr="http://mycompany.com/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/holidays/schemas">
targetNamespace="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:all>
<xs:element name="Holiday" type="holidays:HolidayType"/>
<xs:element name="Employee" type="holidays:EmployeeType"/>
<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: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: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 <<<holidays.xsd>>>.
We can store this file with a convenient name such as <<<hr.xsd>>>.
The WSDL
Which leaves the WSDL. We start our WSDL with the standard preamble, and by importing our existing XSD. To
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/holidays/definitions">>>.
<<<"http://mycompany.com/hr/definitions">>>.
+-------------------------------------
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/holidays/definitions"
xmlns:tns="http://mycompany.com/holidays/definitions"
<xmlns:types="http://mycompany.com/holidays/schemas">
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/holidays/schemas"
schemaLocation="holidays.xsd"/>
<xsd:import namespace="http://mycompany.com/hr/schemas"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
</wsdl:definitions>
@@ -247,15 +255,15 @@ separate the schema from the definition, we will use a separate namespace for th
+-------------------------------------
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/holidays/definitions"
xmlns:tns="http://mycompany.com/holidays/definitions"
xmlns:types="http://mycompany.com/holidays/schemas"
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/holidays/schemas"
schemaLocation="holidays.xsd"/>
<xsd:import namespace="http://mycompany.com/hr/schemas"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="RequestHolidayInput">>
@@ -268,15 +276,15 @@ separate the schema from the definition, we will use a separate namespace for th
+-------------------------------------
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/holidays/definitions"
xmlns:tns="http://mycompany.com/holidays/definitions"
xmlns:types="http://mycompany.com/holidays/schemas"
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/holidays/schemas"
schemaLocation="holidays.xsd"/>
<xsd:import namespace="http://mycompany.com/hr/schemas"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="RequestHolidayInput">
@@ -301,15 +309,15 @@ you use <document/literal> for the <<<soap:binding>>> elements (anything else is
+-------------------------------------
<wsdl:definitions name="HumanResources"
targetNamespace="http://mycompany.com/holidays/definitions"
xmlns:tns="http://mycompany.com/holidays/definitions"
xmlns:types="http://mycompany.com/holidays/schemas"
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/holidays/schemas"
schemaLocation="holidays.xsd"/>
<xsd:import namespace="http://mycompany.com/hr/schemas"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="RequestHolidayInput">
@@ -337,4 +345,4 @@ you use <document/literal> for the <<<soap:binding>>> elements (anything else is
</wsdl:definitions>
+-------------------------------------
This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the next chapter.
This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the {{{tutorial2.html}next section}}.

View File

@@ -0,0 +1,212 @@
-------------------------------------------------------
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-m4-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}}.