diff --git a/src/docbkx/tutorial.xml b/src/docbkx/tutorial.xml index 9e11fcf4..241bec07 100644 --- a/src/docbkx/tutorial.xml +++ b/src/docbkx/tutorial.xml @@ -82,9 +82,658 @@ ]]> - 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. +
+ Data Constract + + Now that we have seen some examples of the XML data that we will use, it makes sense to formalize this into + a schema. This data contract defines the message format we accept. + Basically, there are four different ways of defining such a contract for XML: + + + 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. + + + By far the easiest way to create a XSD is to infer it from sample documents. Any good XML editor or Java IDE + offers this functionality. Basically, these tools use some sample XML documents, and generate a schema from + it that validates them all. The end result certainly needs to be polished up, but it's a great starting + point. + + + Using the sample described above, we end up with the following generated schema: + + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + elementFormDefault="qualified" + targetNamespace="http://mycompany.com/hr/schemas" + xmlns:hr="http://mycompany.com/hr/schemas"> + <xs:element name="HolidayRequest"> + <xs:complexType> + <xs:sequence> + <xs:element ref="hr:Holiday"/> + <xs:element ref="hr:Employee"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="Holiday"> + <xs:complexType> + <xs:sequence> + <xs:element ref="hr:StartDate"/> + <xs:element ref="hr:EndDate"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="StartDate" type="xs:NMTOKEN"/> + <xs:element name="EndDate" type="xs:NMTOKEN"/> + <xs:element name="Employee"> + <xs:complexType> + <xs:sequence> + <xs:element ref="hr:Number"/> + <xs:element ref="hr:FirstName"/> + <xs:element ref="hr:LastName"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="Number" type="xs:integer"/> + <xs:element name="FirstName" type="xs:NCName"/> + <xs:element name="LastName" type="xs:NCName"/> +</xs:schema> + + The generated schema can obviously be improved. The first thing to notice is that 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. + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + The schema still has one problem: with a schema like this, you can expect the following messages to + validate: + + + + + this is not a date + neither is 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 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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + 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. + + + + + xsd:string is used for first and last name. + + + + + + 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 below. + This sectionwill show you how to write your own WSDL, if you choose not to use this functionality. + + + We start our WSDL with the standard preamble, and by importing our existing XSD. To + separate the schema from the definition, we will use a separate namespace for the WSDL definitions: + http://mycompany.com/hr/definitions. + + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/hr/definitions" + xmlns:tns="http://mycompany.com/hr/definitions" + xmlns:types="http://mycompany.com/hr/schemas"> + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/> + </xsd:schema> + </wsdl:types> +</wsdl:definitions> + + + Next, we define our messages based on the written schema. We only have one message: one with the + HolidayRequest we put in the schema: + + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/hr/definitions" + xmlns:tns="http://mycompany.com/hr/definitions" + xmlns:types="http://mycompany.com/hr/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/hr/schemas" + schemaLocation="hr.xsd"/> + </xsd:schema> + </wsdl:types> + <wsdl:message name="RequestHolidayInput">> + <wsdl:part name="body" element="types:HolidayRequest" /> + </wsdl:message> +</wsdl:definitions> + + We add the message to a port type as operation: + + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/hr/definitions" + xmlns:tns="http://mycompany.com/hr/definitions" + xmlns:types="http://mycompany.com/hr/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/hr/schemas" + schemaLocation="hr.xsd"/> + </xsd:schema> + </wsdl:types> + <wsdl:message name="RequestHolidayInput"> + <wsdl:part name="body" element="types:HolidayRequest" /> + </wsdl:message> + <wsdl:portType name="HumanResourcesPortType"> + <wsdl:operation name="RequestHoliday"> + <wsdl:input message="tns:RequestHolidayInput" /> + </wsdl:operation> + </wsdl:portType> +</wsdl:definitions> + + That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part. + 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://example.com/RequestHoliday, but any URI will do), and determine the + location URL where you want request to come in (in this case + http://mycompany.com/humanresources): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + We import the schema defined in . + + + + + We define the RequestHolidayInput message, which gets used in the + portType. + + + + + The HolidayRequest type is defined in the schema. + + + + + We define the HumanResourcesPortType port type, which gets used in the + binding. + + + + + We define the HumanResourcesBinding binding, which gets used in the + port. + + + + + We use a document/literal style. + + + + + The literal http://schemas.xmlsoap.org/soap/http signifies a + HTTP transport. + + + + + 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 + service can be invoked. + + + + + + This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the next section. + +
+
+ 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. + + + The following command creates a Maven2 web application project for us, using the Spring-WS archetype + (i.e. project template) + + +> mvn archetype:create -DarchetypeGroupId=org.springframework.ws \ + -DarchetypeArtifactId=spring-ws-archetype \ + -DarchetypeVersion=1.0-rc1-SNAPSHOT \ + -DgroupId=com.mycompany.hr \ + -DartifactId=holidayService + + + This command will create a new directory called holidayService. In this 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: + + +<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> + + + 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 + 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 it + allows us to select particular parts of the XML JDOM tree, without requiring strict schema conformance. + We extend our endpoint from 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; + } +} + + + + 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 //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. 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. + + + + + + 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. + + + 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: + + + + + holidayEndpoint + + + + + +]]> + + 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 + need to write a WSDL ourselves; Spring-WS can generate one for us based on some conventions. + Here's how we define the generation: + + + + + + + + + + + + + + + + +]]> + + + + 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. + + + + + 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 + this location, you will + see the generated WSDL. + This WSDL is ready to be used by clients, such as 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, you can read the rest of the reference documentation. +