diff --git a/src/docbkx/tutorial.xml b/src/docbkx/tutorial.xml index 1e6f5acc..b497078a 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
@@ -41,8 +41,9 @@ 2006-07-07 ]]> - A holiday consists of a start date and an end date. We decided to use the standard - ISO 8601 date format for the dates, + A holiday consists of a start date and an end date. We decided to use the standard + ISO 8601 + date format for the dates, because that will save a lot of parsing hassle. We also added a namespace to the element, to make sure our elements can used within other XML documents. @@ -60,14 +61,17 @@ ]]> 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. + 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: + 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 - 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 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.
@@ -96,12 +104,26 @@ 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 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: -<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 - 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 + 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 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,38 +544,36 @@
Creating the project - 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. + In this section, we will be using + Maven2 + to create the + initial project structure for us. Doing so is not required, but greatly reduces the amount of code we + have to write to setup our HolidayService. - The following command creates a Maven2 web application project for us, using the Spring-WS archetype - (i.e. project template) - - Until version RC1 of Spring-WS is released, the following has to be added to to - ~/.m2/settings.xml in order to find the archetype: - - spring-s3 - Spring S3 Maven SNAPSHOT Repository - http://s3.amazonaws.com/maven.springframework.org - - true - -]]> - + 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 - + 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.. - 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 + In Spring-WS, you will implement + Endpoints + to handle incoming XML messages. There + are two flavors of endpoints: + message endpoints + and + payload endpoints. + . + Message endpoint gives access to the entire XML message, including SOAP headers, etc. Typically, the + endpoint will only be interested in the + payload + of the message, i.e. the contents of the SOAP body. In that case, creating a payload endpoint makes more sense.
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. @@ -531,80 +646,100 @@ -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; - } -} + 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. - The initialization method init, which sets 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) + The initialization method + init + , which sets 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) 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 - 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 - 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 + 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 + 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. @@ -612,18 +747,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 @@ -654,8 +793,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 + : + @@ -689,20 +834,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: @@ -729,30 +881,43 @@ 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 + . @@ -764,19 +929,24 @@ public class HolidayEndpoint extends AbstractJDomPayloadEndpoint { - You can create a WAR file using mvn install. - If you deploy the application, and point your browser at - this location, you will + You can create a WAR file using + mvn install + . + If you deploy the application, and point your browser at + this location + , you will see the generated WSDL. - This WSDL is ready to be used by clients, such as soapUI, or + This WSDL is ready to be used by clients, such as + soapUI + , or other SOAP frameworks.
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.