From 18d01e3ba5af61dfd0c43086f3dbd777129fda6b Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Sat, 12 May 2007 16:42:15 +0000 Subject: [PATCH] --- src/docbkx/tutorial.xml | 568 ++++++++++++++++++++++++++-------------- 1 file changed, 374 insertions(+), 194 deletions(-) diff --git a/src/docbkx/tutorial.xml b/src/docbkx/tutorial.xml index 163d1033..7eb78aab 100644 --- a/src/docbkx/tutorial.xml +++ b/src/docbkx/tutorial.xml @@ -1,6 +1,6 @@ + "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> Writing Contract-First Web Services
@@ -42,7 +42,8 @@ ]]> A holiday consists of a start date and an end date. We decided to use the standard - ISO 8601 date format for the dates, + 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. @@ -61,13 +62,16 @@ 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. + http://mycompany.com/employees/schemas + .
HolidayRequest - Both the holiday and employee element can be put in a HolidayRequest: + Both the holiday and employee element can be put in a + HolidayRequest + : @@ -82,9 +86,13 @@ ]]> - The order of the two element does not matter: Employee could have been the first + 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. + is the only thing that is important: we are taking a + data-driven + approach.
@@ -96,10 +104,24 @@ Basically, there are four different ways of defining such a contract for XML: - DTDs - XML Schema (XSD) - RELAX NG - Schematron + + DTDs + + + + XML Schema (XSD) + + + + + RELAX NG + + + + + Schematron + + DTDs have limited namespaces support, so they are not suitable for Web services. Relax NG and Schematron are @@ -116,49 +138,54 @@ 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: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:element ref="hr:Holiday"/> + <xs:element ref="hr:Employee"/> </xs:sequence> - </xs:complexType> - </xs:element> - <xs:element name="Holiday"> - <xs:complexType> + </xs:complexType> + </xs:element> + <xs:element name="Holiday"> + <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> - <xs:element name="StartDate" type="xs:NMTOKEN"/> - <xs:element name="EndDate" type="xs:NMTOKEN"/> - <xs:element name="Employee"> - <xs:complexType> + </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: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> + </xs:complexType> + </xs:element> + <xs:element name="Number" type="xs:integer"/> + <xs:element name="FirstName" type="xs:NCName"/> + <xs:element name="LastName" type="xs:NCName"/> + </xs:schema> + - The generated schema can obviously be improved. The first thing to notice is that every type has a root-level - element declaration. This means that the Web service should be able to accept all of these elements as data. This is not - desirable: we only want to accept a HolidayRequest. By removing the wrapping element tags + The generated schema can obviously be improved. The first thing to notice is that every type has a + root-level + element declaration. This means that the Web service should be able to accept all of these elements as data. + This is not + desirable: we only want to accept a + HolidayRequest + . By removing the wrapping element tags (thus keeping the types), and inlining the results, we can accomplish this. - @@ -189,7 +216,7 @@ validate: - + this is not a date neither is this @@ -197,11 +224,24 @@ ... ]]> - 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 NCNames to - strings. 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: + 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: @@ -216,7 +256,7 @@ - @@ -245,41 +285,61 @@ - all tells the XML parser that the order of Holiday and - Employee is not significant. + all + tells the XML parser that the order of + Holiday + and + Employee + is not significant. - We use the xsd:date data type, which consist of a year, month, and day, for - StartDate and EndDate. + We use the + xsd:date + data type, which consist of a year, month, and day, for + StartDate + and + EndDate + . - xsd:string is used for first and last name. + xsd:string + is used for first and last name. - We store this file with as hr.xsd. + We store this file with as + hr.xsd + .
Service contract - A service contract is generally expressed as a WSDL file. - Note that in Spring-WS, writing the WSDL by hand is not required. Based on the XSD and + A service contract is generally expressed as a + WSDL + file. + Note that in Spring-WS, + writing the WSDL by hand is not required + . Based on the XSD and some conventions, Spring-WS can create the WSDL for you, as explained in - . - You can skip to the next section if you want to; the + + . + You can skip to + the next section + if you want to; the remainder of this section will show you how to write your own WSDL by hand. - - + + We start our WSDL with the standard preamble, and by importing our existing XSD. To separate the schema from the definition, we will use a separate namespace for the WSDL definitions: - http://mycompany.com/hr/definitions. + http://mycompany.com/hr/definitions + . ]]> Next, we add our messages based on the written schema types. We only have one message: one with the - HolidayRequest we put in the schema: + HolidayRequest + we put in the schema: @@ -311,17 +372,35 @@ ]]> That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part. - The concrete part consists of a binding, which tells the client how - to invoke the operations you've just defined; and a service, which tells it - where to invoke it. + The concrete part consists of a + binding + , which tells the client + how + to invoke the operations you've just defined; and a + service + , which tells it + where + to invoke it. Adding a concrete part is pretty standard: just refer to the abstract part you defined previously, make sure - you use document/literal for the soap:binding elements - (rpc/encoded is deprecated), pick a soapAction for the operation - (in this case http://mycompany.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): + you use + document/literal + for the + soap:binding + elements + ( + rpc/encoded + is deprecated), pick a + soapAction + for the operation + (in this case + http://mycompany.com/RequestHoliday + , but any URI will do), and determine the + location + URL where you want request to come in (in this case + http://mycompany.com/humanresources + ): @@ -386,30 +465,43 @@ - We import the schema defined in . + We import the schema defined in + + . - We define the HolidayRequest message, which gets used in the - portType. + We define the + HolidayRequest + message, which gets used in the + portType + . - The HolidayRequest type is defined in the schema. + The + HolidayRequest + type is defined in the schema. - We define the HumanResource port type, which gets used in the - binding. + We define the + HumanResource + port type, which gets used in the + binding + . - We define the HumanResourceBinding binding, which gets used in the - port. + We define the + HumanResourceBinding + binding, which gets used in the + port + . @@ -419,19 +511,27 @@ - The literal http://schemas.xmlsoap.org/soap/http signifies a + The literal + http://schemas.xmlsoap.org/soap/http + signifies a HTTP transport. - The soapAction attribute signifies the SOAPAction HTTP + The + soapAction + attribute signifies the + SOAPAction + HTTP header that will be sent with every request. - The http://mycompany.com/humanresources address is the URL where the Web + The + http://mycompany.com/humanresources + address is the URL where the Web service can be invoked. @@ -444,7 +544,9 @@
Creating the project - In this section, we will be using Maven2 to create the + In this section, we will be using + Maven2 + to create the initial project structure for us. Doing so is not required, but greatly reduces the amount of code we have to write to setup our HolidayService. @@ -452,18 +554,26 @@ 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 \ - -DremoteRepositories=http://s3.amazonaws.com/maven.springframework.org - + 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 + - This command will create a new directory called holidayService. In this directory, - there is a src/main/webapp directory, which will contain the root of the WAR file. - You will find the standard web application deployment descriptor WEB-INF/web.xml here, - which defines a Spring-WS MessageDispatcherServlet, and maps all incoming requests + This command will create a new directory called + holidayService + . In this directory, + there is a + src/main/webapp + directory, which will contain the root of the WAR file. + You will find the standard web application deployment descriptor + WEB-INF/web.xml + here, + which defines a Spring-WS + MessageDispatcherServlet + , and maps all incoming requests to this servlet: ]]> - We could have made the servlet more restrictive by using the url pattern /humanresources, + We could have made the servlet more restrictive by using the url pattern + /humanresources + , but this will suffice for now. - Additionally, there is WEB-INF/spring-ws-servlet.xml, which is a Spring application + Additionally, there is + WEB-INF/spring-ws-servlet.xml + , which is a Spring application context 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: message endpoints and - payload endpoints.. + In Spring-WS, you will implement + Endpoints + to handle incoming XML messages. There + are two flavors of endpoints: + message endpoints + and + payload endpoints. + . Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, the - endpoint will only be interested in the payload of the message, i.e. the contents + 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 JDom to handle - the XML message. We are also using XPath, because + In this sample application, we are going to use + JDom + to handle + the XML message. We are also using + XPath + , because it allows us to select particular parts of the XML JDOM tree, without requiring strict schema - conformance. We extend our endpoint from AbstractJDomPayloadEndpoint, + conformance. We extend our endpoint from + AbstractJDomPayloadEndpoint + , because that will give us a JDOM element to execute the XPath queries on. @@ -518,73 +645,91 @@ -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; - 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 HolidayEndpoint(HumanResourceService humanResourceService) { + this.humanResourceService = humanResourceService; + 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; - } -} + humanResourceService.bookHoliday(startDate, endDate, name); + return null; + } + } + - The HolidayEndpoint requires the - HumanResourceService business service to operate, so we + The + HolidayEndpoint + requires the + HumanResourceService + business service to operate, so we use the constructor to inject it. Next, we set up 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 concat(//hr:FirstName,' ',//hr:LastName) + using the JDOM API. There are three expressions: + //hr:StartDate + for + extracting the + >StartDate< + text value, + //hr:EndDate + for + extracting the end date and + concat(//hr:FirstName,' ',//hr:LastName) for extracting and concatenating the names of the employee. - The invokeInternal method is a template method, which gets passed - with the HolidayRequest element from the incoming XML message. We + The + invokeInternal + method is a template method, which gets passed + with the + HolidayRequest + 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 Date objects using a - SimpleDateFormat. With these values, we invoke a method on the + and convert these values to + Date + objects 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 + 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. @@ -592,18 +737,22 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { - Using JDOM is just one of the options to handle the XML, other options include DOM, dom4j, XOM, - SAX, and StAX, but also marshalling techniques like JAXB, Castor, XMLBeans, - JiBX, and XStream. We chose JDOM because it gives us access to the raw XML, and because it - is based on classes (not interfaces and factory methods as with W3C DOM and dom4j), which makes the - code less verbose. We use XPath because it is less fragile than marshalling technologies: we don't - care for strict schema conformance, as long as we can find the dates and the name. - - - Because we use JDOM, we must add some dependencies to the pom.xml, which is in the - root of our project directory. Here is the relevant section of the POM: - - marshalling techniques + like JAXB, Castor, XMLBeans, + JiBX, and XStream. We chose JDOM because it gives us access to the raw XML, and because it + is based on classes (not interfaces and factory methods as with W3C DOM and dom4j), which makes the + code less verbose. We use XPath because it is less fragile than marshalling technologies: we don't + care for strict schema conformance, as long as we can find the dates and the name. + + + Because we use JDOM, we must add some dependencies to the + pom.xml + , which is in the + root of our project directory. Here is the relevant section of the POM: + + org.springframework.ws @@ -634,8 +783,9 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { ]]> - - Here's how we would wire up these classes in our spring-ws-servlet.xml + + Here's how we would wire up these classes in our + spring-ws-servlet.xml application context: ]]> -
-
- 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: - +
+
+ 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 + : + @@ -669,20 +824,27 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { ]]> - - 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. - + + 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 in , we don't + Finally, we need to publish the WSDL. As stated in + + , 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: @@ -709,46 +871,64 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { The bean id determines the URL where the WSDL can be retrieved. In this case, the bean id is - holiday, which means that the WSDL can be retrieved as - holiday.wsdl in the servlet context. The full URL will typically be - http://localhost:8080/holidayService/holiday.wsdl. + holiday + , which means that the WSDL can be retrieved as + holiday.wsdl + in the servlet context. The full URL will typically be + http://localhost:8080/holidayService/holiday.wsdl + . - The schema property is set to the human resource schema we defined in - : we simply placed the schema in the WEB-INF + The + schema + property is set to the human resource schema we defined in + + : we simply placed the schema in the + WEB-INF directory of the application. - Next, we define the WSDL port type to be HumanResource. + Next, we define the WSDL port type to be + HumanResource + . We set the location where the service can be reached: - http://localhost:8080/holidayService. For development, this will suffice, but - obviously we need to change this to http://mycompany.com/humanresources when going + http://localhost:8080/holidayService + . For development, this will suffice, but + obviously we need to change this to + http://mycompany.com/humanresources + when going live. One way to keep this to accomplish this would be to use Spring - PropertyPlaceholderConfigurer. + PropertyPlaceholderConfigurer + . - Finally, we define the target namespace and prefix for the WSDL definition itself. Setting these + Finally, we define the target namespace for the WSDL definition itself. Setting these is not required. If not set, we give the WSDL the same namespace as the schema. - You can create a WAR file using mvn install. + You can create a WAR file using + mvn install + . If you deploy the application, and point your browser at - this location, you will + this location + , you will see the generated WSDL. - This WSDL is ready to be used by clients, such as soapUI, or + This WSDL is ready to be used by clients, such as + soapUI + , or other SOAP frameworks.