This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<chapter id="tutorial">
|
||||
<title>Writing Contract-First Web Services</title>
|
||||
<section>
|
||||
@@ -41,8 +41,9 @@
|
||||
<EndDate>2006-07-07</EndDate>
|
||||
</Holiday>]]></programlisting>
|
||||
<para>
|
||||
A holiday consists of a start date and an end date. We decided to use the standard
|
||||
<ulink url="http://www.cl.cam.ac.uk/~mgk25/iso-time.html">ISO 8601</ulink> date format for the dates,
|
||||
A holiday consists of a start date and an end date. We decided to use the standard
|
||||
<ulink url="http://www.cl.cam.ac.uk/~mgk25/iso-time.html">ISO 8601</ulink>
|
||||
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.
|
||||
</para>
|
||||
@@ -60,14 +61,17 @@
|
||||
</Employee>]]></programlisting>
|
||||
<para>
|
||||
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
|
||||
<literal>http://mycompany.com/employees/schemas</literal>.
|
||||
might make sense to use a different namespace, such as
|
||||
<literal>http://mycompany.com/employees/schemas</literal>
|
||||
.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>HolidayRequest</title>
|
||||
<para>
|
||||
Both the holiday and employee element can be put in a <literal>HolidayRequest</literal>:
|
||||
Both the holiday and employee element can be put in a
|
||||
<literal>HolidayRequest</literal>
|
||||
:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
|
||||
@@ -82,9 +86,13 @@
|
||||
</Employee>
|
||||
</HolidayRequest>]]></programlisting>
|
||||
<para>
|
||||
The order of the two element does not matter: <literal>Employee</literal> 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 <emphasis>data-driven</emphasis> approach.
|
||||
The order of the two element does not matter:
|
||||
<literal>Employee</literal>
|
||||
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
|
||||
<emphasis>data-driven</emphasis>
|
||||
approach.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
@@ -96,12 +104,26 @@
|
||||
Basically, there are four different ways of defining such a contract for XML:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem><para>DTDs</para></listitem>
|
||||
<listitem><para><ulink url="http://www.w3.org/XML/Schema">XML Schema (XSD)</ulink></para></listitem>
|
||||
<listitem><para><ulink url="http://www.relaxng.org/">RELAX NG</ulink></para></listitem>
|
||||
<listitem><para><ulink url="http://www.schematron.com/">Schematron</ulink></para></listitem>
|
||||
<listitem>
|
||||
<para>DTDs</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="http://www.w3.org/XML/Schema">XML Schema (XSD)</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="http://www.relaxng.org/">RELAX NG</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="http://www.schematron.com/">Schematron</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
<para>
|
||||
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.
|
||||
@@ -116,49 +138,54 @@
|
||||
Using the sample described above, we end up with the following generated schema:
|
||||
</para>
|
||||
<programlisting>
|
||||
<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">
|
||||
<emphasis><xs:element name="HolidayRequest"></emphasis>
|
||||
<xs:complexType>
|
||||
<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">
|
||||
<emphasis><xs:element name="HolidayRequest"></emphasis>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:Holiday"/>
|
||||
<xs:element ref="hr:Employee"/>
|
||||
<xs:element ref="hr:Holiday"/>
|
||||
<xs:element ref="hr:Employee"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="Holiday"></emphasis>
|
||||
<xs:complexType>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="Holiday"></emphasis>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:StartDate"/>
|
||||
<xs:element ref="hr:EndDate"/>
|
||||
<xs:element ref="hr:StartDate"/>
|
||||
<xs:element ref="hr:EndDate"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="StartDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="EndDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="Employee"></emphasis>
|
||||
<xs:complexType>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="StartDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="EndDate" type="xs:NMTOKEN"/>
|
||||
<xs:element name="Employee"></emphasis>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="hr:Number"/>
|
||||
<xs:element ref="hr:FirstName"/>
|
||||
<xs:element ref="hr:LastName"/>
|
||||
<xs:element ref="hr:Number"/>
|
||||
<xs:element ref="hr:FirstName"/>
|
||||
<xs:element ref="hr:LastName"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="Number" type="xs:integer"/>
|
||||
<xs:element name="FirstName" type="xs:NCName"/>
|
||||
<xs:element name="LastName" type="xs:NCName"/></emphasis>
|
||||
</xs:schema></programlisting>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<emphasis><xs:element name="Number" type="xs:integer"/>
|
||||
<xs:element name="FirstName" type="xs:NCName"/>
|
||||
<xs:element name="LastName" type="xs:NCName"/></emphasis>
|
||||
</xs:schema>
|
||||
</programlisting>
|
||||
<para>
|
||||
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 <literal>HolidayRequest</literal>. By removing the wrapping element tags
|
||||
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
|
||||
<literal>HolidayRequest</literal>
|
||||
. By removing the wrapping element tags
|
||||
(thus keeping the types), and inlining the results, we can accomplish this.
|
||||
</para>
|
||||
<programlisting>
|
||||
<![CDATA[<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
<![CDATA[<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">
|
||||
@@ -189,7 +216,7 @@
|
||||
validate:
|
||||
</para>
|
||||
<programlisting>
|
||||
<![CDATA[<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
|
||||
<![CDATA[<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
|
||||
<Holiday>
|
||||
<StartDate>this is not a date</StartDate>
|
||||
<EndDate>neither is this</EndDate>
|
||||
@@ -197,11 +224,24 @@
|
||||
...
|
||||
</HolidayRequest>]]></programlisting>
|
||||
<para>
|
||||
Clearly, we must make sure that the start and end date are really dates. XML Schema has an excellent built-in
|
||||
<literal>date</literal> type which we can use. We also change the <literal>NCName</literal>s to
|
||||
<literal>string</literal>s. Finally, we change the <literal>sequence</literal> in
|
||||
<literal>HolidayRequest</literal> to <literal>all</literal>. This tells the XML parser that the order of
|
||||
<literal>Holiday</literal> and <literal>Employee</literal> is not significant. Our final XSD looks like this:
|
||||
Clearly, we must make sure that the start and end date are really dates. XML Schema has an excellent
|
||||
built-in
|
||||
<literal>date</literal>
|
||||
type which we can use. We also change the
|
||||
<literal>NCName</literal>
|
||||
s to
|
||||
<literal>string</literal>
|
||||
s. Finally, we change the
|
||||
<literal>sequence</literal>
|
||||
in
|
||||
<literal>HolidayRequest</literal>
|
||||
to
|
||||
<literal>all</literal>
|
||||
. This tells the XML parser that the order of
|
||||
<literal>Holiday</literal>
|
||||
and
|
||||
<literal>Employee</literal>
|
||||
is not significant. Our final XSD looks like this:
|
||||
</para>
|
||||
<programlistingco>
|
||||
<areaspec>
|
||||
@@ -216,7 +256,7 @@
|
||||
</areaset>
|
||||
</areaspec>
|
||||
<programlisting>
|
||||
<![CDATA[<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
<![CDATA[<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">
|
||||
@@ -245,41 +285,61 @@
|
||||
<calloutlist>
|
||||
<callout arearefs="tutorial.xsd.all">
|
||||
<para>
|
||||
<literal>all</literal> tells the XML parser that the order of <literal>Holiday</literal> and
|
||||
<literal>Employee</literal> is not significant.
|
||||
</para>
|
||||
<literal>all</literal>
|
||||
tells the XML parser that the order of
|
||||
<literal>Holiday</literal>
|
||||
and
|
||||
<literal>Employee</literal>
|
||||
is not significant.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.xsd.dates">
|
||||
<para>
|
||||
We use the <literal>xsd:date</literal> data type, which consist of a year, month, and day, for
|
||||
<literal>StartDate</literal> and <literal>EndDate</literal>.
|
||||
We use the
|
||||
<literal>xsd:date</literal>
|
||||
data type, which consist of a year, month, and day, for
|
||||
<literal>StartDate</literal>
|
||||
and
|
||||
<literal>EndDate</literal>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.xsd.strings">
|
||||
<para>
|
||||
<literal>xsd:string</literal> is used for first and last name.
|
||||
<literal>xsd:string</literal>
|
||||
is used for first and last name.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
</programlistingco>
|
||||
<para>
|
||||
We store this file with as <filename>hr.xsd</filename>.
|
||||
We store this file with as
|
||||
<filename>hr.xsd</filename>
|
||||
.
|
||||
</para>
|
||||
</section>
|
||||
<section id="tutorial-service-contract">
|
||||
<title>Service contract</title>
|
||||
<para>
|
||||
A service contract is generally expressed as a <ulink url="http://www.w3.org/TR/wsdl">WSDL</ulink> file.
|
||||
Note that in Spring-WS, <emphasis>writing the WSDL by hand is not required</emphasis>. Based on the XSD and
|
||||
some conventions, Spring-WS can create the WSDL for you, as explained in
|
||||
<xref linkend="tutorial.implementing.endpoint"/>.
|
||||
You can skip to <link linkend="tutorial-creating-project">the next section</link> if you want to; the
|
||||
A service contract is generally expressed as a
|
||||
<ulink url="http://www.w3.org/TR/wsdl">WSDL</ulink>
|
||||
file.
|
||||
Note that in Spring-WS,
|
||||
<emphasis>writing the WSDL by hand is not required</emphasis>
|
||||
. Based on the XSD and
|
||||
some conventions, Spring-WS can create the WSDL for you, as explained in
|
||||
<xref linkend="tutorial.implementing.endpoint"/>
|
||||
.
|
||||
You can skip to
|
||||
<link linkend="tutorial-creating-project">the next section</link>
|
||||
if you want to; the
|
||||
remainder of this section will show you how to write your own WSDL by hand.
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
<para>
|
||||
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:
|
||||
<uri>http://mycompany.com/hr/definitions</uri>.
|
||||
<uri>http://mycompany.com/hr/definitions</uri>
|
||||
.
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
||||
@@ -294,7 +354,8 @@
|
||||
</wsdl:types>]]></programlisting>
|
||||
<para>
|
||||
Next, we add our messages based on the written schema types. We only have one message: one with the
|
||||
<literal>HolidayRequest</literal> we put in the schema:
|
||||
<literal>HolidayRequest</literal>
|
||||
we put in the schema:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<wsdl:message name="HolidayRequest">
|
||||
@@ -311,17 +372,35 @@
|
||||
</wsdl:portType>]]></programlisting>
|
||||
<para>
|
||||
That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part.
|
||||
The concrete part consists of a <literal>binding</literal>, which tells the client <emphasis>how</emphasis>
|
||||
to invoke the operations you've just defined; and a <literal>service</literal>, which tells it
|
||||
<emphasis>where</emphasis> to invoke it.
|
||||
The concrete part consists of a
|
||||
<literal>binding</literal>
|
||||
, which tells the client
|
||||
<emphasis>how</emphasis>
|
||||
to invoke the operations you've just defined; and a
|
||||
<literal>service</literal>
|
||||
, which tells it
|
||||
<emphasis>where</emphasis>
|
||||
to invoke it.
|
||||
</para>
|
||||
<para>
|
||||
Adding a concrete part is pretty standard: just refer to the abstract part you defined previously, make sure
|
||||
you use <emphasis>document/literal</emphasis> for the <literal>soap:binding</literal> elements
|
||||
(<literal>rpc/encoded</literal> is deprecated), pick a <literal>soapAction</literal> for the operation
|
||||
(in this case <uri>http://mycompany.com/RequestHoliday</uri>, but any URI will do), and determine the
|
||||
<literal>location</literal> URL where you want request to come in (in this case
|
||||
<uri>http://mycompany.com/humanresources</uri>):
|
||||
you use
|
||||
<emphasis>document/literal</emphasis>
|
||||
for the
|
||||
<literal>soap:binding</literal>
|
||||
elements
|
||||
(
|
||||
<literal>rpc/encoded</literal>
|
||||
is deprecated), pick a
|
||||
<literal>soapAction</literal>
|
||||
for the operation
|
||||
(in this case
|
||||
<uri>http://mycompany.com/RequestHoliday</uri>
|
||||
, but any URI will do), and determine the
|
||||
<literal>location</literal>
|
||||
URL where you want request to come in (in this case
|
||||
<uri>http://mycompany.com/humanresources</uri>
|
||||
):
|
||||
</para>
|
||||
<programlistingco>
|
||||
<areaspec>
|
||||
@@ -386,30 +465,43 @@
|
||||
<calloutlist>
|
||||
<callout arearefs="tutorial.wsdl.import">
|
||||
<para>
|
||||
We import the schema defined in <xref linkend="tutorial.xsd"/>.
|
||||
We import the schema defined in
|
||||
<xref linkend="tutorial.xsd"/>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.message">
|
||||
<para>
|
||||
We define the <literal>HolidayRequest</literal> message, which gets used in the
|
||||
<literal>portType</literal>.
|
||||
We define the
|
||||
<literal>HolidayRequest</literal>
|
||||
message, which gets used in the
|
||||
<literal>portType</literal>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.message.part">
|
||||
<para>
|
||||
The <literal>HolidayRequest</literal> type is defined in the schema.
|
||||
The
|
||||
<literal>HolidayRequest</literal>
|
||||
type is defined in the schema.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.portType">
|
||||
<para>
|
||||
We define the <literal>HumanResource</literal> port type, which gets used in the
|
||||
<literal>binding</literal>.
|
||||
We define the
|
||||
<literal>HumanResource</literal>
|
||||
port type, which gets used in the
|
||||
<literal>binding</literal>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.binding">
|
||||
<para>
|
||||
We define the <literal>HumanResourceBinding</literal> binding, which gets used in the
|
||||
<literal>port</literal>.
|
||||
We define the
|
||||
<literal>HumanResourceBinding</literal>
|
||||
binding, which gets used in the
|
||||
<literal>port</literal>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.doclit">
|
||||
@@ -419,19 +511,27 @@
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.transport">
|
||||
<para>
|
||||
The literal <uri>http://schemas.xmlsoap.org/soap/http</uri> signifies a
|
||||
The literal
|
||||
<uri>http://schemas.xmlsoap.org/soap/http</uri>
|
||||
signifies a
|
||||
HTTP transport.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.soapAction">
|
||||
<para>
|
||||
The <literal>soapAction</literal> attribute signifies the <literal>SOAPAction</literal> HTTP
|
||||
The
|
||||
<literal>soapAction</literal>
|
||||
attribute signifies the
|
||||
<literal>SOAPAction</literal>
|
||||
HTTP
|
||||
header that will be sent with every request.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.address">
|
||||
<para>
|
||||
The <uri>http://mycompany.com/humanresources</uri> address is the URL where the Web
|
||||
The
|
||||
<uri>http://mycompany.com/humanresources</uri>
|
||||
address is the URL where the Web
|
||||
service can be invoked.
|
||||
</para>
|
||||
</callout>
|
||||
@@ -444,38 +544,36 @@
|
||||
<section id="tutorial-creating-project">
|
||||
<title>Creating the project</title>
|
||||
<para>
|
||||
In this section, we will be using <ulink url="http://maven.apache.org/">Maven2</ulink> 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.
|
||||
In this section, we will be using
|
||||
<ulink url="http://maven.apache.org/">Maven2</ulink>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
The following command creates a Maven2 web application project for us, using the Spring-WS archetype
|
||||
(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>spring-s3</id>
|
||||
<name>Spring S3 Maven SNAPSHOT Repository</name>
|
||||
<url>http://s3.amazonaws.com/maven.springframework.org</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>]]></programlisting>
|
||||
</para></footnote>
|
||||
The following command creates a Maven2 web application project for us, using the Spring-WS archetype
|
||||
(i.e. project template)
|
||||
</para>
|
||||
<screen>mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
|
||||
-DarchetypeArtifactId=spring-ws-archetype \
|
||||
-DarchetypeVersion=1.0-rc1-SNAPSHOT \
|
||||
-DgroupId=com.mycompany.hr \
|
||||
-DartifactId=holidayService
|
||||
</screen>
|
||||
<screen>mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
|
||||
-DarchetypeArtifactId=spring-ws-archetype \
|
||||
-DarchetypeVersion=1.0-rc1-SNAPSHOT \
|
||||
-DgroupId=com.mycompany.hr \
|
||||
-DartifactId=holidayService \
|
||||
-DremoteRepositories=http://s3.amazonaws.com/maven.springframework.org
|
||||
</screen>
|
||||
<para>
|
||||
This command will create a new directory called <filename>holidayService</filename>. In this directory,
|
||||
there is a <filename>src/main/webapp</filename> directory, which will contain the root of the WAR file.
|
||||
You will find the standard web application deployment descriptor <filename>WEB-INF/web.xml</filename> here,
|
||||
which defines a Spring-WS <classname>MessageDispatcherServlet</classname>, and maps all incoming requests
|
||||
This command will create a new directory called
|
||||
<filename>holidayService</filename>
|
||||
. In this directory,
|
||||
there is a
|
||||
<filename>src/main/webapp</filename>
|
||||
directory, which will contain the root of the WAR file.
|
||||
You will find the standard web application deployment descriptor
|
||||
<filename>WEB-INF/web.xml</filename>
|
||||
here,
|
||||
which defines a Spring-WS
|
||||
<classname>MessageDispatcherServlet</classname>
|
||||
, and maps all incoming requests
|
||||
to this servlet:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
@@ -497,31 +595,48 @@
|
||||
|
||||
</web-app>]]></programlisting>
|
||||
<para>
|
||||
We could have made the servlet more restrictive by using the url pattern <literal>/humanresources</literal>,
|
||||
We could have made the servlet more restrictive by using the url pattern
|
||||
<literal>/humanresources</literal>
|
||||
,
|
||||
but this will suffice for now.
|
||||
</para>
|
||||
<para>
|
||||
Additionally, there is <filename>WEB-INF/spring-ws-servlet.xml</filename>, which is a Spring application
|
||||
Additionally, there is
|
||||
<filename>WEB-INF/spring-ws-servlet.xml</filename>
|
||||
, which is a Spring application
|
||||
context that will contain the Spring-WS bean definitions.
|
||||
</para>
|
||||
</section>
|
||||
<section id="tutorial.implementing.endpoint">
|
||||
<title>Implementing the Endpoint</title>
|
||||
<para>
|
||||
In Spring-WS, you will implement <emphasis>Endpoints</emphasis> to handle incoming XML messages. There
|
||||
are two flavors of endpoints: <link linkend="message-endpoint">message endpoints</link> and
|
||||
<link linkend="payload-endpoint">payload endpoints.</link>.
|
||||
Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, the
|
||||
endpoint will only be interested in the <emphasis>payload</emphasis> of the message, i.e. the contents
|
||||
In Spring-WS, you will implement
|
||||
<emphasis>Endpoints</emphasis>
|
||||
to handle incoming XML messages. There
|
||||
are two flavors of endpoints:
|
||||
<link linkend="message-endpoint">message endpoints</link>
|
||||
and
|
||||
<link linkend="payload-endpoint">payload endpoints.</link>
|
||||
.
|
||||
Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, the
|
||||
endpoint will only be interested in the
|
||||
<emphasis>payload</emphasis>
|
||||
of the message, i.e. the contents
|
||||
of the SOAP body. In that case, creating a payload endpoint makes more sense.
|
||||
</para>
|
||||
<section>
|
||||
<title>Handling the XML Message</title>
|
||||
<para>
|
||||
In this sample application, we are going to use <ulink url="http://www.jdom.org">JDom</ulink> to handle
|
||||
the XML message. We are also using <ulink url="http://www.w3schools.com/xpath/">XPath</ulink>, because
|
||||
<para>
|
||||
In this sample application, we are going to use
|
||||
<ulink url="http://www.jdom.org">JDom</ulink>
|
||||
to handle
|
||||
the XML message. We are also using
|
||||
<ulink url="http://www.w3schools.com/xpath/">XPath</ulink>
|
||||
, because
|
||||
it allows us to select particular parts of the XML JDOM tree, without requiring strict schema
|
||||
conformance. We extend our endpoint from <classname>AbstractJDomPayloadEndpoint</classname>,
|
||||
conformance. We extend our endpoint from
|
||||
<classname>AbstractJDomPayloadEndpoint</classname>
|
||||
,
|
||||
because that will give us a JDOM element to execute the XPath queries on.
|
||||
</para>
|
||||
<programlistingco>
|
||||
@@ -531,80 +646,100 @@
|
||||
<area id="tutorial.endpoint.invokeInternal" coords="38"/>
|
||||
</areaspec>
|
||||
<programlisting>
|
||||
package com.mycompany.hr.ws;
|
||||
package com.mycompany.hr.ws;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
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;
|
||||
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 {
|
||||
public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
|
||||
private XPath startDateExpression;
|
||||
private XPath startDateExpression;
|
||||
|
||||
private XPath endDateExpression;
|
||||
private XPath endDateExpression;
|
||||
|
||||
private XPath nameExpression;
|
||||
private XPath nameExpression;
|
||||
|
||||
private HumanResourceService humanResourceService;
|
||||
private HumanResourceService humanResourceService;
|
||||
|
||||
public HolidayEndpoint(HumanResourceService humanResourceService) {
|
||||
this.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("concat(//hr:FirstName,' ',//hr:LastName)");
|
||||
nameExpression.addNamespace(namespace);
|
||||
}
|
||||
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("concat(//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);
|
||||
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;
|
||||
}
|
||||
}</programlisting>
|
||||
humanResourceService.bookHoliday(startDate, endDate, name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
</programlisting>
|
||||
<calloutlist>
|
||||
<callout arearefs="tutorial.endpoint.constr">
|
||||
<para>
|
||||
The <classname>HolidayEndpoint</classname> requires the
|
||||
<interfacename>HumanResourceService</interfacename> business service to operate, so we
|
||||
The
|
||||
<classname>HolidayEndpoint</classname>
|
||||
requires the
|
||||
<interfacename>HumanResourceService</interfacename>
|
||||
business service to operate, so we
|
||||
use the constructor to inject it.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.endpoint.init">
|
||||
<para>
|
||||
The initialization method <methodname>init</methodname>, which sets up XPath expressions
|
||||
using the JDOM API. There are three expressions: <literal>//hr:StartDate</literal> for
|
||||
extracting the <literal>>StartDate<</literal> text value,
|
||||
<literal>//hr:EndDate</literal> for
|
||||
extracting the end date and <literal>concat(//hr:FirstName,' ',//hr:LastName)</literal>
|
||||
The initialization method
|
||||
<methodname>init</methodname>
|
||||
, which sets up XPath expressions
|
||||
using the JDOM API. There are three expressions:
|
||||
<literal>//hr:StartDate</literal>
|
||||
for
|
||||
extracting the
|
||||
<literal>>StartDate<</literal>
|
||||
text value,
|
||||
<literal>//hr:EndDate</literal>
|
||||
for
|
||||
extracting the end date and
|
||||
<literal>concat(//hr:FirstName,' ',//hr:LastName)</literal>
|
||||
for extracting and concatenating the names of the employee.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.endpoint.invokeInternal">
|
||||
<para>
|
||||
The <methodname>invokeInternal</methodname> method is a template method, which gets passed
|
||||
with the <literal>HolidayRequest</literal> element from the incoming XML message. We
|
||||
use the XPath expressions to extract the string values from the XML messages,
|
||||
and convert these values to <classname>Date</classname> objects using a
|
||||
<classname>SimpleDateFormat</classname>. 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
|
||||
<literal>null</literal>, which indicates to Spring-WS that we don't want to send a
|
||||
The
|
||||
<methodname>invokeInternal</methodname>
|
||||
method is a template method, which gets passed
|
||||
with the
|
||||
<literal>HolidayRequest</literal>
|
||||
element from the incoming XML message. We
|
||||
use the XPath expressions to extract the string values from the XML messages,
|
||||
and convert these values to
|
||||
<classname>Date</classname>
|
||||
objects using a
|
||||
<classname>SimpleDateFormat</classname>
|
||||
. 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
|
||||
<literal>null</literal>
|
||||
, 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.
|
||||
</para>
|
||||
@@ -612,18 +747,22 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
</calloutlist>
|
||||
</programlistingco>
|
||||
<para>
|
||||
Using JDOM is just one of the options to handle the XML, other options include DOM, dom4j, XOM,
|
||||
SAX, and StAX, but also <link linkend="oxm">marshalling techniques</link> 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.
|
||||
</para>
|
||||
<para>
|
||||
Because we use JDOM, we must add some dependencies to the <filename>pom.xml</filename>, which is in the
|
||||
root of our project directory. Here is the relevant section of the POM:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
Using JDOM is just one of the options to handle the XML, other options include DOM, dom4j, XOM,
|
||||
SAX, and StAX, but also
|
||||
<link linkend="oxm">marshalling techniques</link>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
Because we use JDOM, we must add some dependencies to the
|
||||
<filename>pom.xml</filename>
|
||||
, which is in the
|
||||
root of our project directory. Here is the relevant section of the POM:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ws</groupId>
|
||||
@@ -654,8 +793,9 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
</dependency>
|
||||
<dependencies>
|
||||
]]></programlisting>
|
||||
<para>
|
||||
Here's how we would wire up these classes in our <filename>spring-ws-servlet.xml</filename>
|
||||
<para>
|
||||
Here's how we would wire up these classes in our
|
||||
<filename>spring-ws-servlet.xml</filename>
|
||||
application context:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
@@ -668,16 +808,21 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
<bean id="hrService" class="com.mycompany.hr.service.StubHumanResourceService"/>
|
||||
|
||||
</beans>]]></programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Routing the Message to the Endpoint</title>
|
||||
<para>
|
||||
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
|
||||
<interfacename>EndpointMapping</interfacename>. In this tutorial, we will route messages based on
|
||||
their content, by using a <classname>PayloadRootQNameEndpointMapping</classname>. Here's how we
|
||||
wire it up in <filename>spring-ws-servlet.xml</filename>:
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Routing the Message to the Endpoint</title>
|
||||
<para>
|
||||
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
|
||||
<interfacename>EndpointMapping</interfacename>
|
||||
. In this tutorial, we will route messages based on
|
||||
their content, by using a
|
||||
<classname>PayloadRootQNameEndpointMapping</classname>
|
||||
. Here's how we
|
||||
wire it up in
|
||||
<filename>spring-ws-servlet.xml</filename>
|
||||
:
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
|
||||
<property name="mappings">
|
||||
@@ -689,20 +834,27 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
|
||||
</property>
|
||||
</bean>]]></programlisting>
|
||||
<para>
|
||||
This means that whenever a XML message comes in with the namespace
|
||||
<literal>http://mycompany.com/hr/schemas</literal> and the
|
||||
<literal>HolidayRequest</literal> local name, it will be routed to the
|
||||
<varname>holidayEndpoint</varname>.
|
||||
It also adds a <classname>PayloadInterceptor</classname>,
|
||||
which dumps incoming and outgoing messages to the log.
|
||||
</para>
|
||||
<para>
|
||||
This means that whenever a XML message comes in with the namespace
|
||||
<literal>http://mycompany.com/hr/schemas</literal>
|
||||
and the
|
||||
<literal>HolidayRequest</literal>
|
||||
local name, it will be routed to the
|
||||
<varname>holidayEndpoint</varname>
|
||||
.
|
||||
It also adds a
|
||||
<classname>PayloadInterceptor</classname>
|
||||
,
|
||||
which dumps incoming and outgoing messages to the log.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Publishing the WSDL</title>
|
||||
<para>
|
||||
Finally, we need to publish the WSDL. As stated in <xref linkend="tutorial-service-contract"/>, we don't
|
||||
Finally, we need to publish the WSDL. As stated in
|
||||
<xref linkend="tutorial-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:
|
||||
</para>
|
||||
@@ -729,30 +881,43 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
<callout arearefs="tutorial.wsdl.gen.bean">
|
||||
<para>
|
||||
The bean id determines the URL where the WSDL can be retrieved. In this case, the bean id is
|
||||
<varname>holiday</varname>, which means that the WSDL can be retrieved as
|
||||
<filename>holiday.wsdl</filename> in the servlet context. The full URL will typically be
|
||||
<uri>http://localhost:8080/holidayService/holiday.wsdl</uri>.
|
||||
<varname>holiday</varname>
|
||||
, which means that the WSDL can be retrieved as
|
||||
<filename>holiday.wsdl</filename>
|
||||
in the servlet context. The full URL will typically be
|
||||
<uri>http://localhost:8080/holidayService/holiday.wsdl</uri>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.gen.schema">
|
||||
<para>
|
||||
The <varname>schema</varname> property is set to the human resource schema we defined in
|
||||
<xref linkend="tutorial.xsd"/>: we simply placed the schema in the <filename>WEB-INF</filename>
|
||||
The
|
||||
<varname>schema</varname>
|
||||
property is set to the human resource schema we defined in
|
||||
<xref linkend="tutorial.xsd"/>
|
||||
: we simply placed the schema in the
|
||||
<filename>WEB-INF</filename>
|
||||
directory of the application.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.gen.portType">
|
||||
<para>
|
||||
Next, we define the WSDL port type to be <literal>HumanResource</literal>.
|
||||
Next, we define the WSDL port type to be
|
||||
<literal>HumanResource</literal>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.gen.locationUri">
|
||||
<para>
|
||||
We set the location where the service can be reached:
|
||||
<uri>http://localhost:8080/holidayService</uri>. For development, this will suffice, but
|
||||
obviously we need to change this to <uri>http://mycompany.com/humanresources</uri> when going
|
||||
<uri>http://localhost:8080/holidayService</uri>
|
||||
. For development, this will suffice, but
|
||||
obviously we need to change this to
|
||||
<uri>http://mycompany.com/humanresources</uri>
|
||||
when going
|
||||
live. One way to keep this to accomplish this would be to use Spring
|
||||
<classname>PropertyPlaceholderConfigurer</classname>.
|
||||
<classname>PropertyPlaceholderConfigurer</classname>
|
||||
.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="tutorial.wsdl.gen.tns">
|
||||
@@ -764,19 +929,24 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {
|
||||
</calloutlist>
|
||||
</programlistingco>
|
||||
<para>
|
||||
You can create a WAR file using <command>mvn install</command>.
|
||||
If you deploy the application, and point your browser at
|
||||
<ulink url="http://localhost:8080/holidayService/holiday.wsdl">this location</ulink>, you will
|
||||
You can create a WAR file using
|
||||
<command>mvn install</command>
|
||||
.
|
||||
If you deploy the application, and point your browser at
|
||||
<ulink url="http://localhost:8080/holidayService/holiday.wsdl">this location</ulink>
|
||||
, you will
|
||||
see the generated WSDL.
|
||||
This WSDL is ready to be used by clients, such as <ulink url="http://www.soapui.org/">soapUI</ulink>, or
|
||||
This WSDL is ready to be used by clients, such as
|
||||
<ulink url="http://www.soapui.org/">soapUI</ulink>
|
||||
, or
|
||||
other SOAP frameworks.
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
That concludes this tutorial. The tutorial code can be found in the full distribution of Spring-WS.
|
||||
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.
|
||||
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.
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user