Chapter 1. Building SOAP Web-Services

In this chapter, we will cover:

  • Using Maven for building and running a Spring-WS project

  • Creating a data contract

  • Setting up a Web-Service using DispatcherServlet

  • Simplifying the creation of a Web-Service using MessageDispatcherServlet

  • Setting up a Web-Service on JMS transport

  • Setting up a Web-Service on E-mail transport

  • Setting up a Web-Service on embedded HTTP server transport

  • Setting up a Web-Service on XMPP transport

  • Setting up a simple endpoint mapping for the Web-Service

  • Setting up a contract-first Web-Service

  • Setting up an endpoint by annotating the payload-root

  • Setting up a transport-neutral WS-Addressing endpoint

  • Setting up an endpoint using an XPath expression

  • Handling the incoming XML messages using DOM

  • Handling the incoming XML messages using JDOM

  • Handling the incoming XML messages using JAXB2

  • Validating the XML messages on the server side using an interceptor

Introduction

SOAP (Simple Object Access Protocol) was designed to be language-, transport-, and platform-independent, which is an alternative to the old fashioned middleware technologies such as CORBA and DCOM. SOAP was also designed to be extensible. The standards referred to as WS-* — WS-Addressing, WS-Policy, WS-Security, and so on are built on the SOAP protocol.

The Web-Services that use SOAP, along with WSDL and XML schema, have become the standard for exchanging the XML-based messages. The Spring Web-Services facilitate SOAP service development, by providing a comprehensive set of APIs and configurations for the creation of flexible Web-Services. The following diagram shows how a Spring-WS works when it receives an incoming message (the diagram is in abstract form):

Introduction

MessageDispatcher is the central point for a Spring Web-Service and dispatches Web-Service messages to the registered endpoint. In Spring-WS, request/response messages are wrapped inside the MessageContext object and the MessageContext will be passed to the MessageDispatcher (response will be set into MessageContext after invoking the endpoint). When a message arrives, MessageDispatcher uses the request object to get the endpoint. (Mapping a request to an endpoint is called endpoint mapping and it can be done by using data from beans registration within application context, scanning, and autodetection of annotations). Then the MessageDispatcher by using the endpoint, gets endpopint's interceptors (which range from zero to many) and calls handleRequest method on them.

An interceptor (EndpointInterceptor here), as the name suggests, intercepts the request/response to perform some operations prior to (for request)/after (for response) invoking the endpoint. This EndpointInterceptor gets called before/after calling the appropriate endpoint to perform several processing aspects such as logging, validating, security, and so on. Next, MessageDispatcher gets appropriate endpoint adapter for the endpoint method to be called. This adapter offers compatibility with various types of endpoint methods. Each adapter is specialized to call a method with specific method parameter and return type.

And Finally, EndpointAdapter invokes the endpoint's method and transforms the response to the desired form and set it into the MessageContext object. Now the initial message context that was passed to MessageDispatcher, contains the response object, that will be forwarded to the client (by the caller of MessageDispatcher).

Spring-WS only supports the contract-first development style in which creating the contract (XSD or WSDL) is the first step. The required steps to build a contract-first Web-Service using Spring-WS are as follows:

  1. Contract definition (either XSD or WSDL)

  2. Creating endpoint: the class that receives and processes an incoming message.

  3. Configuration of Spring beans and the endpoint.

There are two types of endpoints, namely, payload endpoints and message endpoints. While message endpoints can access the entire XML SOAP envelop, the payload endpoint will only access the payload part of a SOAP envelop, that is, the body of a SOAP envelop. In this book, the focus is on creating payload endpoints.

In this chapter, after a recipe for the explanation of creating contract from a set of XML messages, the major focus will be on implementing endpoints and its related configuration.

For the purpose of illustrating the construction process of Web-Services, this book uses a simple business scenario of a fictitious restaurant, Live Restaurant, which needs to accept online orders from customers. Live Restaurant decides to publish its OrderService component as a Web-Service. For simplicity, just two operations are considered for the OrderService (Java interface).

Introduction

The project will follow the following domain model:

Introduction

Each recipe in this book will incrementally build parts of the project to make it a complete Web-Service application. The Java project name is LiveRestaurant, and each recipe will use a slightly different version of the project, with the extension _R-x.x. For example, the first recipe in this chapter will use LiveRestaurant_R-1.1 for the Web-Service server and LiveRestaurant_R-1.1-Client for the client as the project name.

Note

Setting up a Web-Service is the goal of this chapter, so more emphasis is on explanation of the server-side code and settings. Client-side code is used in this chapter for checking the functionality of the server. More about client side code, settings, and testing will be discussed in the following chapters.

Using Maven for building and running a Spring-WS project

Recent modern software development, based on enterprise-grade open source technologies, requires a new generation of build and project management tools. Such tools can make a standard way for building, managing, and deploying small scale to large scale applications.

Maven, hosted by the Apache Software Foundation, is a project management and automated build and deploy tool. Maven is built upon Ant's features and adds several features such as feature dependency and project management. Maven was initially used for Java programming, but it can also be used to build and manage projects written in other programming languages. In recent years, Maven has been used to automate the process of building, managing, and testing the deployments of major open source projects.

This recipe details the steps required to set up Maven for building, testing, and deploying the projects used in this book.

Getting ready

This recipe requires the installation of the following software or tools:

  1. Java 6 or higher and Maven 3.0.2: For download and installation, refer to http://maven.apache.org/ and http://www.oracle.com/technetwork/java/javase/downloads/index.html.

  2. Add your custom repositories to settings.xml under MAVEN_HOME/conf or .m2 folders (MAVEN_HOME is the folder in which Maven is installed and .m2 is the folder in which Maven downloads its artifacts to).

    Later, you can add an extra repository to your custom repositories. You can disable this repository by setting activeByDefault to false (the file that contains repositories is in the resources folder):

    <profile>
    <id>my-repository</id>
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    <!-- list of standard repository -->
    <repositories>
    ...
    ...
    <repository>
    <id>maven2-repository.java.net</id>
    <name>Java.net Repository for Maven</name>
    <url>http://download.java.net/maven/2</url>
    </repository>
    ....
    <repository>
    <id>maven1-repository.java.net</id>
    <name>Java.net Repository for Maven</name>
    <url>http://download.java.net/maven/1</url>
    </repository>
    </repositories>
    </profile>
    

An alternative way to include the Maven repositories to your Maven build is to include repository data in the POM file directly. Samples of both ways to include repositories are included under the Using Maven folder in the resource bundle of this chapter.

How to do it...

  1. Build and deploy a project.

    mvn clean package tomcat:run
    
  2. Browse the following Web-Service WSDL file:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  3. The following is the browser's output:

    <wsdl:definitions
    targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
    <wsdl:types>
    <schema elementFormDefault="qualified" targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
    <element name="placeOrderRequest">
    <complexType>
    <sequence>
    <element name="order" type="QOrder:Order" />
    </sequence>
    </complexType>
    ........
    </schema>
    </wsdl:types>
    .......
    <wsdl:binding name="OrderServiceSoap11" type="tns:OrderService">
    <soap:binding style="document"
    transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="placeOrder">
    <soap:operation soapAction="" />
    <wsdl:input name="placeOrderRequest">
    <soap:body use="literal" />
    </wsdl:input>
    <wsdl:output name="placeOrderResponse">
    <soap:body use="literal" />
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="cancelOrder">
    <soap:operation soapAction="" />
    <wsdl:input name="cancelOrderRequest">
    <soap:body use="literal" />
    </wsdl:input>
    <wsdl:output name="cancelOrderResponse">
    <soap:body use="literal" />
    </wsdl:output>
    </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="OrderServiceService">
    <wsdl:port binding="tns:OrderServiceSoap11" name="OrderServiceSoap11">
    <soap:address
    location="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" />
    </wsdl:port>
    </wsdl:service>
    </wsdl:definitions>
    
  4. The following is the output of the Maven command:

...........
[INFO] Building war: C:...LiveRestaurant.war
.......
[INFO] --- tomcat-maven-plugin:1.1:run ...@ LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration ...
Oct 15,...org.apache.catalina.startup.Embedded start
INFO: Starting tomcat server
Oct 15...org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
org.apache.catalina.core.ApplicationContext log
...Set web app root ..: 'webapp.root' = [...srcmainwebapp]
INFO: Initializing log4j from..WEB-INFlog4j.properties]
...
INFO: Initializing Spring FrameworkServlet 'spring-ws'
......
INFO .. - FrameworkServlet 'spring-ws': initialization ..
Oct .. org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Oct .. org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080

Note

In order to import a Maven project into an Eclipse IDE:

Go to the root of the project (chapterOneLiveRestaurant_R-1.1) and execute:

mvn eclipse:eclipse -Declipse.projectNameTemplate="LiveRestaurant_R-1.1"

Then, you can import the Maven project as an Eclipse project.

In case Maven cannot find a JAR file, you can use your custom repository using the following command:

mvn -P my-repository clean package tomcat:run

How it works...

mvn clean package installs the required components into a local repository and creates a WAR/JAR file of the project:

[INFO] Building war: ...LiveRestaurant.war

mvn tomcat:run runs a WAR file of the project on the Tomcat plugin. mvn jetty:run runs the WAR file of the project on the Jetty plugin:

INFO] --- tomcat-maven-plugin:1.1:... LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration at

Creating a data contract

A WSDL document, known as a service contract, provides a standard way in which a Web-Service client and server exchange data. Using WSDL, the client and server could be on a different application or platform. XML Schema Definition(XSD), known as data contract, describes the structure of the datatypes that are being exchanged between the Web-Service server and client. XSD describes the types, fields, and any validation on those fields (such as max/min or pattern, and so on). While WSDL is specific to the Web-Service and describes a Web-Service's artifacts, such as methods and data passed through these methods (WSDL itself uses an XSD for that), URL, and so on; XSD only presents the structure of the data.

To be able to set up a Spring Web-Service, we need a contract. There are four different ways of defining such a contract for XML:

  • DTDs

  • XML Schema (XSD)

  • RELAX NG

  • Schematron

DTDs have limited namespace support, so they are not suitable for Web-Services. RELAX NG and Schematron certainly are easier than XML Schema. Unfortunately, they are not so widely supported across platforms. Spring-WS uses XML Schema.

A data contract is the center of Spring-WS and a service contract can be generated from a data contract. The easiest way to create an XSD is to infer it from the 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. In this recipe, we will discuss sample XML data massages and how to convert them into a single schema file. The generated schema is used in this book as a data contract.

Getting ready

  1. Install Java (as described in the first recipe).

  2. Install xmlbeans-2.5.0 from http://xmlbeans.apache.org/.

  3. The resources for this recipe are included in the folder Create Data Contract.

How to do it...

  1. Copy your XML messages (placeOrderRequest.xml, placeOrderResponse, cancelOrderRequest.xml, and cancelOrderResponse.xml) to the xmlbeans-2.5.0in working folder.

  2. Run the following command:

    inst2xsd -design rd -enumerations never placeOrderRequest.xml placeOrderResponse.xml cancelOrderRequest
    
    
  3. The preceding command creates the schema0.xsd schema file. The generated schema result certainly needs to be modified, but it's a great starting point. Here is the final polished schema (orderService.xsd):

    <?xml version="1.0" encoding="UTF-8"?>
    ......
    <schema...">
    <element name="placeOrderRequest">
    <complexType>
    <sequence>
    <element name="order" type="QOrder:Order"></element>
    </sequence>
    </complexType>
    </element>
    <element name="placeOrderResponse">
    <complexType>
    <sequence>
    <element name="refNumber" type="string"></element>
    </sequence>
    </complexType>
    </element>
    .........
    data contractdata contractcreating<complexType name="Order">
    <sequence>
    <element name="refNumber" type="string"></element>
    <element name="customer" type="QOrder:Customer"></element>
    <element name="dateSubmitted" type="dateTime"></element>
    <element name="orderDate" type="dateTime"></element>
    <element name="items" type="QOrder:FoodItem"
    maxOccurs="unbounded" minOccurs="1">
    </element>
    </sequence>
    </complexType>
    <complexType name="Customer">
    <sequence>
    <element name="addressPrimary" type="QOrder:Address"></element>
    <element name="addressSecondary" type="QOrder:Address"></element>
    <element name="name" type="QOrder:Name"></element>
    </sequence>
    </complexType>
    ....
    </schema>
    

How it works...

Initially, the input and output sample messages are required. In this book, there are four XML messages (placeOrderRequest.xml, placeOrderResponse, cancelOrderRequest.xml, and cancelOrderResponse.xml) and all the recipes use these message data formats for communication. Inst2xsd generates a schema file from the existing XML sample messages. Resources of this recipe are included under the Create Data Contract folder in the resource bundle of this chapter.

Setting up a Web-Service using DispatcherServlet

Spring-WS provides one of the easiest mechanisms to develop Web-Services in the Java platform. This recipe focuses on building a very simple Web-Service using the Spring-MVC DispatcherServlet and the components provided by Spring-WS.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.2 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

  1. Copy the service contract from the resources folder (orderService.wsdl).

  2. Create an endpoint (OrderSeviceMessageReceiverEndpoint).

  3. Configure the endpoint, service contract, WebServiceMessageReceiverHandlerAdapter, MessageDispatcher, and WsdlDefinitionHandlerAdapter, in the server Spring configuration file (Dispatcher-servlet.xml) .

  4. Configure DispatcherServlet inside the web.xml file.

  5. Run the server using the following command:

    mvn clean package tomcat:run
    
    
    • The following is the output:

    ...........................
    [INFO] Running war on http://localhost:8080/LiveRestaurant
    ...................................
    18-Oct-2011 10:23:02.....ApplicationContext log
    INFO: Initializing Spring FrameworkServlet 'Dispatcher'
    18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol init
    INFO: Initializing Coyote HTTP/1.1 on http-8080
    18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol start
    INFO: Starting Coyote HTTP/1.1 on http-8080
    
    
  6. To browse your service WSDL, open the following link inside your browser:

    http://localhost:8080/LiveRestaurant/Dispatcher/OrderService.wsdl
    
  7. To test, open a new command window, go to the folder LiveRestaurant_R-1.2-Client, and run the following command:

mvn clean package exec:java

  • The following is the server-side output:

Inside method, OrderSeviceMethodEndpoint.receive - message content = <?xml version="1.0" encoding="UTF-8"?><tns:placeOrderRequest xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

How it works...

DispatcherServlet receives all the incoming requests, and based on request context, it forwards the request to the endpoint (the general form of a request URL is http://<host>:<port>/<appcontext>/<requestcontext> (here appcontext is Liverestaurant and requestcontext should start with /Dispatcher/). The requests context that ends with /OrderService go to OrderSeviceMessageReceiverEndpoint and requests that end with *.wsdl go to SimpleWsdl11Definition).

DispatcherServlet configured in web.xml is responsible for receiving all requests with a URL mapping [/Dispatcher/*].

<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/Dispatcher/*</url-pattern>
</servlet-mapping>

You can change the URL pattern to suit your requirement.

DispatcherServlet plays a major role in intercepting the HTTP requests and then loads the Spring bean configuration file. By default, it detects the bean configuration file by name<servlet-name>-servlet.xml. Since we have named the DispatcherServlet as Dispatcher in web.xml file, the server looks for Dispatcher-servlet.xml as application context filename. You may configure another file, using the following context param in the web.xml:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>

DispatcherServlet needs separate instances of WebServiceMessageReceiverHandlerAdapter, MessageDispatcher, and WsdlDefinitionHandlerAdapter that in this recipe are configured inside Dispatcher-servlet.xml. The DispatcherServlet, by default, delegates to controllers for handling requests, but in the configuration file, it is configured to delegate to a MessageDispatcher (WebServiceMessageReceiverHandlerAdapter). SaajSoapMessageFactory is a specific message factory for message creation in Spring-WS.

<beans ...">
<bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter">
<property name="messageFactory">
<bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"></bean>
</property>
</bean>
.......

To let DispatcherServlet handle the WSDL contract, WsdlDefinitionHandlerAdapter, which is registered in the configuration file; it reads the WSDL file source using the WsdlDefinition implementation (SimpleWsdl11Definition) and writes that as the result to the HttpServletResponse.

SimpleUrlHandlerMapping is to redirect the client requests to the appropriate endpoints using the URL patterns. Here the request URL that ends with *.wsdl will be redirected to sampleServiceDefinition (that is, SimpleWsdl11Definition that uses OrderService.wsdl to generate the response), and if the request URL contains /OrderService, it will be redirected to OrderSeviceMessageReceiverEndpoint. SOAPMessageDispatcher is to dispatch a SOAP message to the registered endpoint(s) (OrderSeviceMessageReceiverEndpoint).

.......
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="*.wsdl">sampleServiceDefinition</prop>
<prop key="/OrderService">OrderServiceEndpoint</prop>
</props>
</property>
<property name="defaultHandler" ref="messageDispatcher"/>
</bean>
<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMessageReceiverEndpoint"/>
<bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>
<bean id="sampleServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
<property name="wsdl" value="/WEB-INF/OrderService.wsdl"/>
</bean>
</beans>
OrderSeviceMessageReceiverEndpoint is a very basic endpoint that get incoming message (messageContext.getRequest().getPayloadSource()) and prin it out:
........
public class OrderSeviceMessageReceiverEndpoint implements
WebServiceMessageReceiver {
public OrderSeviceMessageReceiverEndpoint() {
}
public void receive(MessageContext messageContext) throws Exception {
System.out
.println("Inside method, OrderSeviceMethodEndpoint.receive - message content = "
+ xmlToString(messageContext.getRequest().getPayloadSource()));
}

Note

You can change the URL pattern to suit your requirement.

private String xmlToString(Source source) {
try {
StringWriter stringWriter = new StringWriter();
Result result = new StreamResult(stringWriter);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return stringWriter.getBuffer().toString();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
return null;
}
}

See also

The Setting up a Web-Service using MessageDispatcherServlet recipe in this chapter.

Simplifying the creation of a Web-Service using MessageDispatcherServlet

MessageDispatcherServlet is the core component of Spring-WS. With simple configuration, a Web-Service can be set up in minutes. This servlet came as a simple way to configure an alternative to the Spring-MVC DispatcherServlet. As in the second recipe, Setting up a Web-Service using DispatcherServlet, DispatcherServlet needs separate instances of WebServiceMessageReceiverHandlerAdapter, MessageDispatcher, and WsdlDefinitionHandlerAdapter. However, MessageDispatcherServlet can dynamically detect EndpointAdapters, EndpointMappings, EndpointExceptionResolvers, and WsdlDefinition by setting inside the application context.

Since this is the default method for configuring Spring Web-Services, it will be used in later recipes. In this recipe, a very basic implementation of setting up a Spring-WS is detailed. More advance implementation will be explained later in the recipe Setting up a contract-first Web-Service.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.3 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

  1. Copy the service contract from the resources folder (orderService.wsdl).

  2. Create an endpoint (OrderSeviceMethodEndpoint).

  3. Configure the endpoint. The service contract is in the server Spring configuration file (spring-ws-servlet.xml).

  4. Configure MessageDispatcherServlet inside the web.xml file.

  5. Run the server using the following command:

    mvn clean package tomcat:run
    
    
    • The following is the output after the server is run successfully:

    ...........................
    [INFO] >>> tomcat-maven-plugin:1.1:run .. LiveRestaurant >>>
    [..............
    [INFO] Running war on http://localhost:8080/LiveRestaurant
    [I...........
    ..XmlBeanDefinitionReader.. Loading..spring-ws-servlet.xml]
    ...
    ..SimpleMethodEndpointMapping#0, OrderService, OrderServiceEndpoint]; root of factory hierarchy
    INFO [main] (SaajSoapMessageFactory.java:135) -..
    INFO [main] (FrameworkServlet.java:320) - FrameworkServlet '
    ........
    INFO: Starting Coyote HTTP/1.1 on http-8080
    
    
  6. To browse your service WSDL, open the following link in your browser:

    http://localhost:8080/LiveRestaurant/spring-ws/OrderService.wsdl
    
  7. To test, open a new command window, go to the folder LiveRestaurant_R-1.3-Client, and run the following command:

mvn clean package exec:java

  • The following is the server-side output:

Sent response
...
<tns:placeOrderResponse....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
.....
for request
<tns:placeOrderRequest.... >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
....

How it works...

The MessageDispatcherServlet is configured in the web configuration file web.xml:

<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>
org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Note

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.

MessageDispatcherServlet is the central element that handles the incoming SOAP requests, with the help of other components (EndpointAdapters, EndpointMappings, EndpointExceptionResolvers, and WsdlDefinition). It combines the attributes of both DispatcherServlet and MessageDispatcher that dispatch to the appropriate endpoint. This is the standard servlet recommended to build Web-Services with Spring-WS.

Since the MessageDispatcherServlet is inherited from FrameworkServlet, it looks for a configuration file named<servlet-name>-servlet.xml in the class path (you can change the configuration filename using the context-param, contextConfigLocation settings in the web.xml, as described in the recipe Setting up a Web-Service using DispatcherServlet). In the example, since the servlet name in the web.xml file is set to Spring-WS, the file spring-ws-servlet.xml is the Web-Services configuration file.

MessageDispatcherServlet then looks up for an endpoint mapping element in the configuration file, for the purpose of mapping the client requests to the endpoint. Here,<sws:static-wsdl sets the data contract in the WSDL format. This is the element to be configured in spring-ws-servlet.xml to set up a Web-Service:

<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
<property name="endpoints">
<ref bean="OrderServiceEndpoint"/>
</property>
<property name="methodPrefix" value="handle"></property>
</bean>
<sws:static-wsdl id="OrderService" location="/WEB-INF/orderService.wsdl"/>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint">
</bean>

The example uses SimpleMethodEndpointMapping that maps the client requests to MethodEnpoints. It maps the incoming request to a method that starts with the handle+root element of the message (handle+placeOrderRequest). In the endpoint class (OrderSeviceMethodEndpoint), a method with the name handleplaceOrderRequest should be defined.

In this method, the parameter source includes the incoming message and input parameters to call order service could be extracted from this parameter, then the method calls to the orderService method and wraps the outgoing message in the StringSource that is to be sent back to the client:

public class OrderSeviceMethodEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public @ResponsePayload
Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName,lName,refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

The endpoint mappings will be detailed in the later recipes.

See also

The recipes Setting up a Web-Service using DispatcherServlet, Setting up a simple endpoint mapping for the Web-Service, and Setting up a contract-first Web-Service discussed in this chapter.

Setting up a Web-Service on JMS transport

HTTP is the most common Web-Service protocol. However, Web-Services are currently built on multiple transports, each with different scenarios.

JMS was included in Java 2, J2EE by Sun Microsystems in 1999. Using JMS, systems are able to communicate synchronously or asynchronously and are based on point-to-point and publish-subscribe models. SOAP over JMS inherits the JSM features and meets the following requirements:

  • Where asynchronous messaging is required

  • Where the message consumers are slower than the producers

  • To guarantee the delivery of messages

  • To have a publisher/subscriber(multiple) model

  • When sender/receiver might be disconnected

Spring Web-Services provide features to set up a Web-Service over JMS protocol that is built upon the JMS functionality in the Spring framework. In this recipe, how to set up a Spring-WS over JMS is presented.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.4 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • spring-jms-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • log4j-1.2.9.jar

  • jms-1.1.jar

  • activemq-core-4.1.1.jar

In this recipe, Apache ActiveMQ is used to set up a JMS server and to create JMS server-related objects (queue and broker are used here). Spring-WS family JARs provide a functionality to set up a Spring-WS and spring-jms and jms JARs provide the JMS functionality that the Spring-WS, over JMS, is built upon it.

How to do it...

  1. Create an endpoint (OrderSeviceMethodEndpoint).

  2. Configure the MessageListenerContainer, MessageListener, and connectionFactory in the Spring configuration file (applicationContext.xml).

  3. Configure MessageDispatcher that includes the endpoint mappings inside applicationContext.xml.

  4. Run the recipe project using the following command:

    mvn clean package
    
    
  5. The following is the output once the project runs successfully:

INFO [main] (SaajSoapMessageFactory.java:135) -..
INFO [main] (DefaultLifecycleProcessor.java:330) -..
INFO [main] .. - ActiveMQ 4.1.1 JMS Message Broker (localhost)..
..
INFO [JMX connector] ..
INFO [main]..ActiveMQ JMS Message Broker ..started
INFO [main] ..- Connector vm://localhost Started
.....
Received response ....
<tns:placeOrderResponse ..><tns:refNumber>..</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest ....>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
........

How it works...

DefaultMessageListenerContainer listens to destinationName (RequestQueue) for incoming messages. When a message arrives, this listener will use the message factory (messageFactory) to extract the message and use the dispatcher (messageDispatcher) to dispatch the message to the endpoint (SimplePayloadEndpoint).............

In the application context, WebServiceMessageListener is a listener inside MessageListenerContainer. The message container uses connectionfactory to connect to the destination (RequestQueue):

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
</bean>
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" value="RequestQueue"/>
<property name="messageListener">
<bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
<property name="messageFactory" ref="messageFactory"/>
<property name="messageReceiver" ref="messageDispatcher"/>
</bean>
</property>
</bean>

This listener uses message Dispatcher and messageFactory to receive incoming messages and to send outgoing SOAP messages. Inside messageDiapatcher, endpoint's mapping is included, which sets the endpoint (SimplePayloadEndpoint) and type of endpoint mapping (PayloadRootQNameEndpointMapping):

<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint">
<bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint">
<property name="orderService">
<bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/>
</property>
</bean>
</property>
</bean>
</property>
</bean>

The invoke method from the endpoint (SimplePayloadEndpoint) will be called when a request comes to the server, and the response will be returned to be sent back to the client:

public class SimplePayloadEndpoint implements PayloadEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public Source invoke(Source request) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

JmsTransportWebServiceIntegrationTest is included in the project to load the application context, set up the JMS server, and test the Web-Service. However, these details are not discussed here. The client of JMS transport will be discussed in the next chapter.

See also

The Creating a Web-Service client on JMS transport recipe discussed in Chapter 2, Building Clients for SOAP Web-Services and the Exposing Web-Services using JMS as the underlying communication protocol recipe discussed in Chapter 10,Spring Remoting.

Setting up a Web-Service on E-mail transport

HTTP is easy to understand and therefore has been most often defined and implemented, but it's clearly not the most suitable transport for Web-Services in any scenario.

Web-Service on E-mail transport can take advantage of store-and-forward messaging to provide an asynchronous transport for SOAP. In addition, there is no firewall concern on e-mail and those applications that are able to communicate together don't need web servers to set up a Web-Service. This allows SOAP, over mail transport, to be used in a number of scenarios where HTTP is not suitable.

The reasons why setting up a Web-Service over HTTP is not suitable and e-mail might be a solution as a transport protocol are listed as follows:

  • If a system is protected by a firewall, there is no control over the HTTP request/response, but e-mail is always is accessible.

  • If a system expects no request/response conventional model. For example, publish/subscriber model is required.

  • If a request takes too long to complete. For example, if the server has to run complex and time-consuming services, the client would get an HTTP timeout error. In such a scenario, Web-Service over e-mail is more appropriate.

In this recipe, setting up a Web-Service over E-mail transport is presented. To load the application context and test the Web-Service, a test class is used. This class also starts up and shuts down the server.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.5 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • mail-1.4.1.jar

  • mock-javamail-1.6.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

Setting up a mail server outside a system that is using JavaMail for testing purpose is difficult. Mock JavaMail addresses this issue and provides a pluggable component to the system using JavaMail. The system can use this component to send/receive e-mails against the temporary in-memory mailbox.

How to do it...

  1. Create an endpoint (SimplePayloadEndpoint).

  2. Configure MessageReceiver and MessageDispatcher that include endpoint mappings inside applicationContext.xml.

  3. Run the recipe project using the following command:

mvn clean package

  • The following is the output:

........
INFO [main] ...- Creating SAAJ 1.3 MessageFactory with SOAP 1.1 Protocol
..- Starting mail receiver [imap://[email protected]/INBOX]
....
Received response...
<tns:placeOrderResponse xmlns:tns="....">
<tns:refNumber>...</tns:refNumber></tns:placeOrderResponse>
...for request ..
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
......

How it works...

Messages sent to an address will be saved in an inbox. The message receiver (messageReceiver) monitors the inbox at continuous intervals and as soon as it detects a new E-mail, it reads the E-mail, extracts the message, and forwards the message to a message dispatcher (messageDispatcher). The message dispatcher will call the invoke method inside its default endpoint (SamplePayloadEndpoint), and inside the handler method (invoke), the response will be sent back to the client.

When the application context is being loaded, MailMessageReceiver starts up a mail receiver and its inbox folder (imap://[email protected]/INBOX), that is, a temporary in-memory inbox. After loading the application context, the messageReceiver bean acts as a server monitor for the incoming messages based on a pluggable strategy (monotoringStrategy) that monitors the INBOX folder (imap://[email protected]/INBOX) for new messages on pollingInterval of 1000 ms. storeUri is the location to be monitored for the incoming messages (imap://[email protected]/INBOX) and transportUri is the mail server for sending the responses:

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
<property name="messageFactory" ref="messageFactory"/>
<property name="from" value="[email protected]"/>
<property name="storeUri" value="imap://[email protected]/INBOX"/>
<property name="transportUri" value="smtp://smtp.packtpubtest.com"/>
<property name="messageReceiver" ref="messageDispatcher"/>
<property name="session" ref="session"/>
<property name="monitoringStrategy">
<bean class="org.springframework.ws.transport.mail.monitor.Pop3PollingMonitoringStrategy">
<property name="pollingInterval" value="1000"/>
</bean>
</property>
</bean>

Inside messageDiapatcher, endpoint mapping is included that sets the endpoint (SimplePayloadEndpoint) and type of the endpoint mapping (PayloadRootQNameEndpointMapping):

<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint">
<bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint">
<property name="orderService">
<bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/>
</property>
</bean>
</property>
</bean>
</property>
</bean>

SimplePayloadEndpoint receives a request and returns a fixed dummy response using OrderService. When a request comes to the server, the invoke method will be called and the response will be returned that is to be sent back to the client:

public class SimplePayloadEndpoint implements PayloadEndpoint {
OrderService orderService;
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public Source invoke(Source request) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}

To test this recipe, a webServiceTemplate is used. We will discuss it in the next chapter.

MailTransportWebServiceIntegrationTest is included in the project to load the application context, set up the mail server, and to test the Web-Service.

See also

The Creating Web-Service client on E-mail transport recipe, discussed in Chapter 2,BuildingClients for SOAP Web-Services.

Setting up a Web-Service on embedded HTTP transport

External HTTP servers might be able to provide several features, but they are not light and they need a configuration to set up.

Spring-WS provides a feature to set up an HTTP-based Web-Service using embedded Sun's JRE 1.6 HTTP server. The embedded HTTP server is a light-weight standalone server that could be used as an alternative to external servers. While configuration of the web server is a must in a conventional external server (web.xml), the embedded HTTP server doesn't need any deployment descriptor to operate and its only requirement is to configure an instance of the server through the application context.

In this recipe, setting up a Spring Web-Service on the embedded HTTP server is presented. Since there is no external HTTP server, a Java class is used to load application context and start up the server.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.6 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

  1. Copy the service contract (OrderService.wsdl) from the resource folder.

  2. Create a service and an implementation of it and annotate its implementation with @Service("serviceName") (OrderSevice,OrderServiceImpl).

  3. Configure the service in the application context (applicationContext) that is to be scanned and detected automatically.

  4. Configure the embedded HTTP server inside the application context.

  5. Add a Java class with the main method to load the application context to set up the embedded HTTP server.

  6. Run the server using the following command:

    mvn clean package exec:java
    
    
  7. From LiveRestaurant_R-1.6-Client, run the following command:

mvn clean package exec:java

  • The following is the output when the server runs successfully:

<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
<tns:customer>
.......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

  • The following is the client-side output:

<tns:placeOrderResponse ...><refNumber>order-John_Smith_1234</refNumber></tns:placeOrderResponse>>
......
.....

How it works...

In the application context, SimpleHttpFactoryBean creates a simple HTTP server (from embedded Sun's JRE 1.6) and it starts the HTTP server on initialization and stops it on destruction.

The HTTP server that has a context property sets up a Web-Service with the service class (orderServiceImpl) set as the endpoint and specifies the URL defined by the properties inside the context (localhost:3478/OrderService). This service interface is registered within the context property.

However, the service implementation is autodetected using component-scan. HttpInvokerProxyFactoryBean creates a client's proxy for a specific server URL.

<context:annotation-config />
<context:component-scan base-package="com.packtpub.liverestaurant.service.endpoint" />
<bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
<property name="contexts">
<util:map>
<entry key="/OrderService">
<bean class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
<property name="serviceInterface" value="com.packtpub.liverestaurant.service.endpoint.IOrderServiceEndPoint" />
<property name="service" ref="orderServiceImpl" />
</bean>
</entry>
</util:map>
</property>
<property name="port" value="3478" />
<property name="hostname" value="localhost" />
</bean>

IOrderServiceEndPointImpl and IOrderServiceEndPoint are simple service interface and implementation classes. IOrderServiceEndPointImpl is annotated by @Service (orderServiceImpl) and is to be detected as a service implementation.

package com.packtpub.liverestaurant.service.endpoint;
public interface OrderService {
String invoke(String request) throws Exception;
}
package com.packtpub.liverestaurant.service.endpoint;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
@Service("orderServiceImpl")
public class OrderServiceImpl implements OrderService {
static Logger logger = Logger.getLogger(OrderServiceImpl.class);
private static final String responseContent = "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><refNumber>Order Accepted!</refNumber></tns:placeOrderResponse>";
public String invoke(String request) throws Exception {
logger.info("invoke method request:"+request);
return responseContent;
}
}

ServerStartUp.java is used to load the application context and start up the server:

package com.packtpub.liverestaurant.server;
public class ServerStartUp {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
System.out.println(appContext);
char c;
// Create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("Enter any character to quit.");
c = (char) br.read();
appContext.close();
}

Setting up Spring-WS on XMPP transport

HTTP is most often used as a Web-Service transport protocol. However, it is not able to meet the asynchronous communication requirements.

Web-Service on XMPP transport is capable of asynchronous communication in which a client doesn't need to wait for a response from a service; instead, the service sends the response to the client when the process is completed. Spring-WS 2.0 includes XMPP (Jabber) support in which a Web-Service can communicate over the XMPP protocol. In this recipe, setting up a Spring-WS on XMPP transport is presented. Since there is no external HTTP server, a test class is used to load the application context.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.7, which has the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • spring-ws-support-2.0.1.RELEASE.jar

  • spring-test-3.0.5.RELEASE.jar

  • junit-4.7.jar

  • xmlunit-1.1.jar

  • smack-3.1.0.jar

How to do it...

  1. Create an endpoint (SamplePlayLoadEndPoint).

  2. Configure connection to the XMPP server in the application context (applicationContext.xml).

  3. Configure the message receiver in the application context.

  4. Run the following command:

mvn clean package

  • The following is the response received:

<placeOrderRequest xmlns="..."><id>9999</id></placeOrderRequest>
...
for request
...<placeOrderRequest xmlns="...."><id>9999</id></placeOrderRequet>...

How it works...

In the application context, the messageFactory bean is responsible for creating the incoming and outgoing SOAP messages. The messageReceiver bean acts as a server, using a connection (to XMPP server:google talk), and listens to the host on a specific service with a username and password.

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
<property name="host" value="talk.google.com"/>
<property name="username" value="[email protected]"/>
<property name="password" value="yourPassword"/>
<property name="serviceName" value="gmail.com"/>
</bean>
<bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
<property name="messageFactory" ref="messageFactory"/>
<property name="connection" ref="connection"/>
<property name="messageReceiver" ref="messageDispatcher"/>
</bean>

Once the message is sent by the client, it will be forwarded to the endpoint (SamplePlayLoadEndPoint that is configured within messageDispatcher) by the message dispatcher and the response will be returned to the client:

<bean id="messageDispatcher"
class="org.springframework.ws.soap.server.SoapMessageDispatcher">
<property name="endpointMappings">
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="defaultEndpoint"> <bean class="com.packtpub.liverestaurant.service.endpoint.SamplePlayLoadEndPoint"/>
</property> </bean>
</property>
</bean>

Webservicetemplate is used here as a client; it will be discussed in the next chapter.

SamplePlayLoadEndPoint just receives a request and returns a response:

public class SamplePlayLoadEndPoint implements PayloadEndpoint {
static Logger logger = Logger.getLogger(SamplePlayLoadEndPoint.class);
public Source invoke(Source request) throws Exception {
return request;
}

A test class is included in the project to load the application context, set up the XMPP Web-Service server, and test the Web-Service.

See also

The Creating Web-Service client on XMPP transport recipe discussed in Chapter 2,Clients for SOAP Web-Services.

Setting up a contract-first Web-Service

Generating WSDL and XSD contracts from Java code and setting up a Web-Service is called contract-last development. The major drawback to this approach is the contracts (WSDL or XSD) of the Web-Service could eventually change if there are any changes in Java classes. In this way, the client side has to update the client-side classes and that always is not favorable. The contract-first approach was introduced as an alternative to tackle the contract-last's bottleneck. In the contract-first approach, the contract (WSDL or schema) are primary artifacts to set up a Web-Service.

Some of the advantages of the contract-first approach over contract-last are as follows:

  • Performance: In contract-last, some extra data, that is, serialization of Java code might be exchanged between client and server, which decreases the performance, while contract-last precisely exchanges the required data and maximizes the performance.

  • Consistency: Different vendors may generate different WSDL in the contract-last approach, while the contract-first approach eliminates this problem by standing on the same contract.

  • Versioning: Changing the version of a contract-last Web-Service means changing Java classes in both client and server side and that might eventually be expensive in case there are a lot of clients that call a Web-Service, while in contract-first, since the contract is decoupled from implementation, versioning could be simply done by adding a new method implementation in the same endpoint class or using a stylesheet to convert an old message format into new message format.

  • Maintenance/enhancement cost: Changing only a contract is much cheaper than changing Java code in both client and server side. In this recipe, we will discuss how to set up a contract-first Web-Service using Spring-WS.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.8, with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

How to do it...

  1. Copy the data contract (orderService.xsd) from the resources folder.

  2. Create an endpoint (OrderEndpoint).

  3. Configure the auto-detection of the endpoint using the component scan in the server Spring configuration file (spring-ws-servlet.xml).

  4. Configure the dynamic generation of WSDL from the data contract (orderService.xsd).

  5. Run the server using the following command:

    mvn clean package tomcat:run
    
    
  6. Browse to the following link to see the WSDL:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  7. Run client from LiveRestaurant_R-1.8-Client:

mvn clean package

  • The following is the output when the server runs successfully:

Sent response....
<tns:placeOrderResponse xmlns:tns="...."><tns:refNumber>tns:refNumber>order-John_S
mith_9999</tns:refNumber></tns:refNumber></
tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="....">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

How it works...

The steps of this recipe are the same as that of the recipe Simplifying the creation of a Web-Service using MessageDispatcherServlet, except the implementation of endpoint handling methods.

This annotation serves as a specialization of @Component, allowing for the implementation classes to be autodetected through classpath scanning, which is configured in the server application context file (spring-ws-servlet.xml):

<context:component-scan base-package="com.packtpub.liverestaurant.service"/>
<sws:annotation-driven/>

OrderEndpoint is the endPoint of this recipe and the @Endpoint annotation is also the same as @service, allowing for the implementation classes to be autodetected through classpath scanning. A request with the root element placeOrderRequest (localPart = "placeOrderRequest") and the namespace http://www.packtpub.com/liverestaurant/OrderService/schema will be forwarded to call the corresponding method (handlePlaceOrderRequest).

@Endpoint
public class OrderEndpoint {
private static final Log logger = LogFactory.getLog(OrderEndpoint.class);
private static final String NAMESPACE_URI = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest")
@ResponsePayload
public Source handlePancelOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception {
String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent();
String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent();
String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent();
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName,lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

Other details about annotations and how the request will be mapped to an endpoint method are contained in this chapter.

The following setting in the spring-ws-servlet.xml file causes the application to automatically generate the WSDL file from the data contract (orderService.xsd).

<sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService"
targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<sws:xsd location="/WEB-INF/orderService.xsd"/>
</sws:dynamic-wsdl>

Note

Even though WSDL can be generated automatically from the data contract (XSD), Spring-WS recommends avoiding autogeneration of WSDL for these reasons:

  • To keep consistency between releases (there might be slight differences among autogenerated WSDLs for different versions)

  • Autogeneration of WSDL is slow, although once generated, WSDL will be cached and used later.

Therefore, Spring-WS recommends, while developing, autogenerate WSDL once via the browser and save it and use static WSDL to expose the service contract.

See also

The recipes Setting up an endpoint by annotating the payload-root, Simplifying the creation of a Web-Service using MessageDispatcherServlet, discussed in this chapter and the Creating a Web-Service client on HTTP transport recipe, discussed in Chapter 2,Building Clients forSOAP Web-Services.

Also see the recipes discussed in Chapter 10, Spring Remoting, to find out how to set up contract-last Web-Services.

Setting up a simple endpoint mapping for the Web-Service

This recipe demonstrates a very simple endpoint mapping that maps a Web-Service request to a Java class method.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.9, with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

How to do it...

The steps of this recipe are the same as that of the previous recipe, Setting up a contract-first Web-Service, except that the registration of the endpoint, that is, method endpoint mapping and is configured in spring-ws-servlet.xml.

  1. Define an endpoint (OrderSeviceMethodEndpoint) based on the method mapping standard (SimpleMethodEndpointMapping).

  2. Configure the method endpoint mapping in spring-ws-servlet.xml.

  3. Run the mvn clean package tomcat:run command and browse to see the WSDL:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  4. To test, open a new command window, go to Liverestaurant_R-1.9-Client, and run the following command:

mvn clean package exec:java

  • Here is the server-side output:

Sent response ..
<tns:placeOrderResponse xmlns:tns="..."><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

How it works...

SimpleMethodEndpointMapping maps from the local name of the request payload (placeOrderRequest) to the methods of the POJO classes. Here is a sample of the request payload (note the local name of the request payload):

<tns:placeOrderRequest ...>
<tns:order>
......
</tns:order>
</tns:placeOrderRequest>

The endpoint bean is registered using the endpoints property. This property tells you that there should be a method in the endpoint class (OrderServiceEndpoint) with a name that starts with methodPrefix(handle) and ends with the request payload local name (placeOrderRequest). This increases the flexibility of the endpoint naming by using the configuration in spring-ws-servlet.xml:

<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping">
<property name="endpoints">
<ref bean="OrderServiceEndpoint"/>
</property>
<property name="methodPrefix" value="handle"></property>
<property name="interceptors">
<list>
<bean
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
<property name="logRequest" value="true" />
<property name="logResponse" value="true" />
</bean>
</list>
</property>
</bean>
<bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint">
</bean>

The endpoint method name should match the handle+request message root name (handleplaceOrderRequest). In the body of the method, we should process the request and finally return the response in the form of javax.xml.transform.Source:

public class OrderSeviceMethodEndpoint {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public @ResponsePayload Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

See also

The recipes Setting up a transport-neutral WS-Addressing endpoint and Setting up an endpoint by annotating the payload-root, discussed in this chapter.

Setting up an endpoint by annotating the payload-root

Spring-WS simplifies the creation of complex Web-Services further by its annotation features and reduces the code and configuration in XML.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.10, with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

How to do it...

The steps of this recipe are the same as that of Setting up a contract-first Web-Service and here we want to describe the endpoint mapping using annotation in the endpoint class.

  1. Run the following command:

    mvn clean package tomcat:run
    
    
  2. Browse to the following link to see the WSDL:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  3. To test, open a new command window, go to LiveRestaurant-1.10-Client, and run the following command:

mvn clean package exec:java

  • Here is the server-side output:

Sent response ..
<tns:placeOrderResponse xmlns:tns="..."><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

How it works...

By including component scan and annotation-driven settings in the Spring-WS configuration file (spring-ws-servlet.xml), the Spring container will scan the entire package for endpoints, services, and dependencies to inject and autowire each other to build the Web-Service blocks. You cannot see the adapters and other handlers here, since the container smartly picks the right/default adapter, dynamically (messageDispatcher runs support method of an adapter from a list of existing adapters for the endponit, and if support method returns true, that adapter is the right adapter):

<context:component-scan base-package="com.packtpub.liverestaurant.service"/>
<sws:annotation-driven/>
<sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService"
targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema">
<sws:xsd location="/WEB-INF/orderService.xsd"/>
</sws:dynamic-wsdl>

The @Endpoint annotation of OrderSeviceAnnotationEndpoint makes it an endpoint, with PayloadRootAnnotationMethodEndpointMapping, with the exact pointers to the method-endpoint mapping with the method-level annotations:

@Endpoint
public class OrderSeviceAnnotationEndpoint {
private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderSeviceAnnotationEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleCancelOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
boolean cancelled =true ;
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>");
}

@PayloadRoot helps the MessageDispatcher to map the request to the method, with the help of an argument annotation, @RequestPayload, which specifies the exact message payload part of the entire SOAP message as an argument into the method (it finds the method by root element of a request equal to localPart, for example, placeOrderRequest or placeCancelRequest). @RequestPayload tells the container that the argument RequestPayload is to be extracted from the SOAP message and injected to the method as an argument at runtime.

The return type annotation, @ResponsePayload, instructs MessageDispatcher that the instance of javax.xml.transform.Source is ResponsePayload. The smart Spring-WS framework detects the type of these objects at runtime and delegates to the appropriate PayloadMethodProcessor. In this case, it is SourcePayloadMethodProcessor, since the input argument and the return value are of the type javax.xml.transform.Source.

See also

The recipes Setting up a transport-neutral WS-Addressing endpoint and Setting up a simple endpoint mapping for the Web-Service, discussed in this chapter.

Setting up a transport-neutral WS-Addressing endpoint

Using HTTP transport information inside the XML messages for routing messages to endpoints mixes data and operation together, and these messages will be replied to for the requested client.

WS-Addressing standardizes routing mechanism by separating routing data and including it inside the SOAP headers. WS-Addressing may use its own metadata instead of using HTTP transport data for endpoint routing. In addition, a request from a client may return to a different client in WS-Addressing. For example, considering the following request from a client, the client side can set ReplyTo to its own address and FaultTo to admin the endpoint address. Then, the server will send successful messages to the client and fault messages to the admin address [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">.

<SOAP-ENV:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:To>server_uri</wsa:To>
<wsa:Action>action_uri</wsa:Action>
<wsa:From>client_address </wsa:From>
<wsa:ReplyTo>client_address</wsa:ReplyTo>
<wsa:FaultTo>admen_uri </wsa:FaultTo>
<wsa:MessageID>..</wsa:MessageID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest>....</tns:placeOrderReques>
</SOAP-ENV:Body></SOAP-ENV:Envelope>]

In this recipe, we will set up a Spring-WS using WS-Addressing.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.11 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

How to do it...

The steps of this recipe are the same as that of Setting up an endpoint by annotating the payload-root, except for the endpoint class. So, follow the steps of the mentioned recipe and define a new endpoint with WS-Addressing standards.

  1. Run the following command:

    mvn clean package tomcat:run
    
    
  2. To test, open a new command window to Liverestaurant_R-1.11-Client and run the following command:

mvn clean package exec:java
  • The following is the server-side output:

Sent response [<SOAP-ENV:Envelope ...><SOAP-ENV:Header...>
<wsa:To ...>http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReqResponse</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:2beaead4-c04f-487c-86fc-caab64ad8461</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...><tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>...
for request <SOAP-ENV:Envelope ..><SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header><SOAP-ENV:Body>
<tns:placeOrderRequest ...>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
...
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>

How it works...

Same as the previous recipe, Setting up an endpoint by annotating the payload-root, the incoming WS-Addressing SOAP messages will be forwarded to the endpoint (OrderEndpoint, which is autodetected by @Endpoint). As you can see from the output, a header is added to the SOAP envelop that WS-Addressing uses for mapping and dispatching purposes of the endpoint method.

<SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header>

In this recipe, the server applies AnnotationActionEndpointMapping, which uses @Action (http://www.packtpub.com/OrderService/OrdReq). @Action is similar to @PayloadRoot for recognizing the handling methods (handleOrderRequest) in the endpoint (OrderEndpoint) .

@Endpoint
public class OrderEndpoint {
private OrderService orderService;
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
@Action("http://www.packtpub.com/OrderService/OrdReq")
public @ResponsePayload
Source handleOrderRequest(@RequestPayload Source source) throws Exception {
//extract data from input parameter
String fName="John";
String lName="Smith";
String refNumber="1234";
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
}

See also

The recipe Creating Web-Service client for WS-Addressing endpoint, discussed in Chapter 2, Building Clients for SOAP Web-Services, and the recipe Setting up an endpoint by annotating the payload root, discussed in this chapter.

Setting up an endpoint using an XPath expression

Spring-WS allows us to extract the passed parameters in the endpoint method's signature using annotations with the XPath expressions. For example, in the endpoint method's handleOrderRequest (@RequestPayload Source source), if you want to find the value of any element in the source object, you have to use Java API to extract the value. You can eliminate using Java API in the handler method by using XPath in the method's signature to extract the data from the incoming XML data, as shown as follows: handleOrderRequest(@XPathParam("/OrderRequest/message") String message).

This recipe illustrates the usage of XPath expressions in endpoint mapping with the help of annotation.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.12 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-12.9.jar

How to do it...

The steps of this recipe are the same as that of Setting up an endpoint by annotating the payload-root, except for the implementation of endpoint handling methods. So, follow the steps of the mentioned recipe and use XPath expressions to extract data from incoming message and create a response.

  1. Run the following command from LiveRestaurant_R-1.12:

    mvn clean package tomcat:run
    
    
  2. Browse to the following link to see the Web-Service service contract:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  3. To test, open a new command window, go to LiveRestaurant_R-1.12-Client, and run the following command:

mvn clean package exec:java
s
  • The following is the server-side output:

Sent response ..
<tns:placeOrderResponse xmlns:tns="">
<tns:refNumber>order-John_Smith_9999</tns:refNumber>
</tns:placeOrderResponse>
...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<order>
<refNumber>9999</refNumber>
<customer>
......
</customer>
<dateSubmitted>2008-09-29T05:49:45</dateSubmitted>
<orderDate>2014-09-19T03:18:33</orderDate>
<items>
<type>Snacks</type>
<name>Pitza</name>
<quantity>2</quantity>
</items>
</order>
</tns:placeOrderRequest>
...
Sent response...
<tns:cancelOrderResponse xmlns:tns="...">
<tns:cancelled>true</tns:cancelled>
</tns:cancelOrderResponse>
...
for request ...
<tns:cancelOrderRequest xmlns:tns="...">
<refNumber>9999</refNumber>
</tns:cancelOrderRequest>

How it works...

Passing the method parameter is the same as the recipe Setting up an endpoint by annotating the payload-root, except that it uses @XPathParam, which specifies the path of the data in a message that is to be passed as an argument into the method. Here XpathParamMethodArgumentResolver is responsible for extracting the value from the message and passing it to the method.

The annotation XpathParam helps the MethodArgumentResolvers (XpathParamMethodArgumentResolver) to extract information out of the XML and binds a node value to a method argument (using // cause, the whole message is searched recursively, for example, //lName searches the whole placeRequestRequest message). The same implementation is used for the method cancelOrderRequest:

@Endpoint
public class OrderEndpoint {
private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema";
private OrderService orderService;
@Autowired
public OrderEndpoint(OrderService orderService) {
this.orderService = orderService;
}
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleOrderRequest(@XPathParam("//fName") String fName,@XPathParam("//lName") String lName,@XPathParam("//refNumber") String refNumber) throws Exception {
return new StringSource(
"<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>" + orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>");
}
@PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handleCancelOrderRequest(@XPathParam("//refNumber") String refNumber) throws Exception {
boolean cancelled = orderService.cancelOrder(refNumber);
return new StringSource(
"<tns:cancelOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>");
}

The method argument can be any of the following:

  • boolean or Boolean

  • double or Double

  • String

  • Node

  • NodeList

See also

The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter.

Handling the incoming XML messages using DOM

The implementation of the endpoint requires us to get the incoming XML messages and extract its data. In Java, there are various methods (W3C DOM, SAX, XPath, JAXB, Castor, XMLBeans, JiBX, or XStream) for extracting data from an input XML message, but most of them are not language-neutral.

DOM was created to be language-neutral and initially used for JavaScript manipulation of HTML pages. In Java, W3C DOM library is provided to interact with XML data. Classes, such as org.w3c.dom.Document, org.w3c.dom.Element, org.w3c.dom.Node, and org.w3c.dom.Text from W3C DOM library, are for extracting data from an input XML message.

In this recipe, W3C DOM is used to extract the data from incoming messages.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.13, with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

The steps of this recipe are the same as that of the recipe Setting up an endpoint by annotating the payload-root, except for the implementation of the endpoint-handling methods. So, follow the steps of the mentioned recipe and use DOM to extract data from the incoming message and create the response.

  1. Run the command mvn clean package tomcat:run and browse to the following link:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  2. To test, open a new command window and run the following command:

mvn clean package exec:java

  • The following is the server-side output:

Sent response ....
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber></placeOrderResponse>
...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.... </tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
,

How it works...

Passing the method parameter is the same as the recipe Setting up an endpoint by annotating the payload-root, except that we use @RequestPayload, which specifies the DOM element of data in a message to be passed as an argument into the method. Here, DomPayloadMethodProcessor is responsible for extracting the value from the message and passing it to the method. Since the return type which is specified by @ResponsePayload is also a DOM element type, DomPayloadMethodProcessor is being used as return handler.

The @PayloadRoot annotation informs Spring-WS that the handleCancelOrderRequest method is a handling method for XML messages. The sort of message that this method can handle is indicated by the annotation values (the @RequestPayload element tells it is of the DOM element type). In this case, it can handle XML elements that have the placeOrderRequest local part and the http://www.packtpub.com/liverestaurant/OrderService/schema namespace.

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest")
@ResponsePayload
public Element handlePlaceOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception {
String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent();
String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent();
String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent();

The preceding code extracts the elements refNumber, fName, and lName from the incoming XML message (placeOrderRequest) via the method getElementsByTagNameNS. Then, it finds and returns the text content of the first item in the refNumber, fName, and lName elements (by item(0).getTextContent()).

The following part of the code creates an outgoing XML message by creating the placeOrderResponse element (using document.createElementNS). Then, it creates the child element refNumber (using document.createElementNS) and creates the text of this element (using createTextNode and appendChild). Then, it appends the refNumber element to the response element placeOrderResponse (using the appendChild method):

Document document = documentBuilder.newDocument();
Element responseElement = document.createElementNS(NAMESPACE_URI,
"placeOrderResponse");
Element canElem=document.createElementNS(NAMESPACE_URI,"refNumber");
Text responseText = document.createTextNode(orderService.placeOrder(fName, lName, refNumber));
canElem.appendChild(responseText);
responseElement.appendChild(canElem);
return responseElement;

See also

The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter and the recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,Building Clients for SOAP Web-Services.

Handling the incoming XML messages using JDOM

Implementation of endpoints requires us to get the incoming XML messages and extract its data. DOM can fetch the data from an XML document, but it is slow and memory consuming and has very basic features.

JDOM document is not built into the memory; it is built on demand (lazy initialization design pattern). In addition, JDOM makes navigating through the document tree or manipulating the elements easier by providing a standard Java-based collection interface. In this recipe, JDOM is used to extract data from incoming messages.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.14, with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • jdom-1.0.jar

  • log4j-1.2.9.jar

  • jaxen-1.1.jar

  • xalan-2.7.0.jar

How to do it...

The steps of this recipe are the same as that of the Setting up an endpoint by annotating the payload-root recipe, except for the implementation of the endpoint-handling methods. So, follow the steps of the aforementioned recipe, use JDOM to extract the data from incoming message, and create the response.

  1. Run the following command:

    mvn clean package tomcat:run
    
    
  2. Browse to the following link:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  3. To test, open a new command window and run the following command:

mvn exec:java exec:java

  • The following is the server-side output:

Sent response ...
<tns:placeOrderResponse xmlns:tns="...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest xmlns:tns="....">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>

How it works...

It works in the same way as explained in the previous recipe, except that it uses JDOM in its method implementation.

The following part of the code extracts the values refNumber, fName, and lName from the incoming XML message (placeOrderRequest) by using namespace and XPath object:

Namespace namespace = Namespace.getNamespace("tns", NAMESPACE_URI);
XPath refNumberExpression = XPath.newInstance("//tns:refNumber");
refNumberExpression.addNamespace(namespace);
XPath fNameExpression = XPath.newInstance("//tns:fName");
fNameExpression.addNamespace(namespace);
XPath lNameExpression = XPath.newInstance("//tns:lName");
lNameExpression.addNamespace(namespace);
String refNumber = refNumberExpression.valueOf(placeOrderRequest);
String fName = fNameExpression.valueOf(placeOrderRequest);
String lName = lNameExpression.valueOf(placeOrderRequest);

The following part of the code creates an outgoing message by creating the placeOrderResponse element (using new Element(...)). Then, it creates the child element refNumber (using new Element(...)) and creates the text of this element (using setText(...)). Then, it appends the message element to the response element placeOrderResponse (using the addContent method):

Namespace resNamespace = Namespace.getNamespace("tns", NAMESPACE_URI);
Element root = new Element("placeOrderResponse", resNamespace);
Element message = new Element("refNumber", resNamespace);
message.setText(orderService.placeOrder(fName, lName, refNumber));
root.addContent(message);
Document doc = new Document(root);
return doc.getRootElement();

See also

The recipes Setting up an endpoint by annotating the payload-root and Handling incoming XML Messages using DOM, discussed in this chapter.

The recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,Building Clients for SOAP Web-Services.

Handling the incoming XML messages using JAXB2

Java Architecture for XML Binding (JAXB) is a Java standard for Object-XML marshalling. JAXB defines a programmer API for reading and writing Java objects to / from XML documents. The object-XML mapping is generally annotated in classes. JAXB provides a set of useful annotations with the default values for most of them that make this marshalling an easy job.

This recipe demonstrates how to handle the incoming XML message in a Web-Service using JAXB in a very simple way. For simplicity and a continuation from the previous recipes, the same recipes are re-used with little improvements in converting the XML schema into domain classes to demonstrate the usage of JAXB.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.15 and has the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

The steps of this recipe are the same as that of the recipe Setting up an endpoint by annotating the payload-root, except for the implementation of the endpoint handling methods. So, follow the steps of the aforementioned recipe and use JAXB Marshaller/Un-Mashaller to convert payload into/from POJO.

  1. First, we define a set of domain objects we need to marshal to/from XML from the data contract OrderService.xsd (refer to the recipe Marshalling with JAXB2, discussed in Chapter 6,

  2. Change the implementation of the endpoint (OrderEndpoint) to use JAXB.

  3. Run the following command:

    mvn clean package tomcat:run
    
    
  4. Browse to the following link:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  5. To test, open a new command window to Liverestaurant_R-1.15-Client and run the following command:

mvn clean package exec:java

  • The following is the server-side output:

Sent response ....
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>....
....
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>

How it works...

In the preceding code, the XML is bound with Java classes at runtime using JAXB. The incoming XML is converted into Java objects (unmarshalling) and after processing the objects, the resultant objects are marshalled back to XML before returning to the caller:

@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS)
public @ResponsePayload
Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception {
PlaceOrderRequest request = (PlaceOrderRequest) unmarshal(source, PlaceOrderRequest.class);
PlaceOrderResponse response = new PlaceOrderResponse();
String refNumber=request.getOrder().getRefNumber();
String fName=request.getOrder().getCustomer().getName().getFName();
String lName=request.getOrder().getCustomer().getName().getLName();
response.setRefNumber(orderService.placeOrder(fName,lName,refNumber));
return marshal(response);
}
private Object unmarshal(Source source, Class clazz) throws JAXBException {
JAXBContext context;
try {
context = JAXBContext.newInstance(clazz);
Unmarshaller um = context.createUnmarshaller();
return um.unmarshal(source);
} catch (JAXBException e) {
e.printStackTrace();
throw e;
}
}
private Source marshal(Object obj) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
return new JAXBSource(context, obj);
}

The JAXB context binds the Java classes passed via the constructor with the incoming XML, with the help of annotations in the classes at runtime, which instructs the unmarshaller to instantiate and load data from the XML tags into the objects. The objects are now passed to the service classes (OrderServiceImpl) for processing:

public class OrderServiceImpl implements OrderService {
@Service
public class OrderServiceImpl implements OrderService {
public String placeOrder( String fName,String lName,String refNumber){
return "order-"+fName+"_"+lName+"_"+refNumber;
}
public boolean cancelOrder( String refNumber ){
return true;
}

This approach allows the developer to work with Java objects instead of XML code with simple marshalling technology.

See also

The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter.

The recipe Marshalling with JAXB2, discussed in Chapter 6,Marshalling and Object-XMLMapping (OXM) — Converting POJO to/from XML messages using Marshallers andUn-Marshallers.

Validating the XML messages at the server side using an interceptor

Data contract is a basic concept used to set up a Spring-WS. However, validation is a basic requirement before a SOAP message sends/replies on the server side/client side.

Spring-WS supports validation of messages on the server side as well as the client side. In this recipe, server-side validation is applied and when the incorrect request comes to the server or the incorrect response replays from the server to the client, it throws an exception.

Getting ready

In this recipe, the project's name is LiveRestaurant_R-1.16 with the following Maven dependencies:

  • spring-ws-core-2.0.1.RELEASE.jar

  • log4j-1.2.9.jar

How to do it...

The steps of this recipe are the same as that of Handling the incoming XML messages using DOM, except for the validation of request/response message.

  1. Modify spring-ws-servlet.xml to include PayloadValidatingInterceptor.

  2. Run the following command:

    mvn clean package tomcat:run
    
    
  3. Browse to the following link:

    http://localhost:8080/LiveRestaurant/OrderService.wsdl
    
  4. To test, open a new command window to Liverestaurant_R-1.16-Client and run the following command:

mvn clean package exec:java

  • The following is the server-side output:

Sent response [...
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
<tns:addressPrimary>
.....
</tns:addressPrimary>
.......
</tns:customer>
........
</tns:order>
</tns:placeOrderRequest>
WARN [http-8080-1] (AbstractFaultCreatingValidatingInterceptor.java:156) - XML validation error on request: cvc-complex-type.2.4.a: Invalid content was found s
tarting with element 'tns:address'. One of '{"http://www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.
........ Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http:
//www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>]

How it works...

spring-ws-servlet.xml is almost the same, as described in the recipe Handling the incoming XML messages using DOM, except that it includes the interceptor that uses schema for validation, validateRequest, and validateResponse.

<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="schema" value="/WEB-INF/OrderService.xsd"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
</bean>
</sws:interceptors>

When running the client, two requests will be sent to the server. The first one will be processed and the response will be sent back to the client, while the second one contains the wrong element (address instead of addressPrimary) that will send the faulty response back:

Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http: //www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>]
....

See also

The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter.

The recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,BuildingClients for SOAP Web-Services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset