Follow my blog with Bloglovin

Saturday, December 31, 2011

Spring Web Services 2.0 step-by-step explained end-to-end

Spring Web Services 2.0 Step by Step Publishing Web Service and client for the web service.

Here, we will crate a web service using contract first approach. In contract first approach we first finalize the contact (WSDL, message types for request/response), so the interface is clear and is exact what is required and less likely to change and implementation detail (Java classes and interface) at last. For more on contract first approach http://static.springsource.org/spring-ws/site/reference/html/why-contract-first.html.

Envirenment:
Eclipse 3.6 JEE
JDK 1.6
Spring WS 2.0.3
Spring framework 3.1
Apache Tomact 6.0.26

Step #1 Download Spring Web Service release 2.0.3 and its dependencies Spring framework 3 higher from
www.springsource.org/download. This also requires Java 5 or higher.

Unzip both downloaded files. You can find library jars in following locations:
<Unzip_Dir>\spring-ws-2.0.3.RELEASE\dist\ and in modules directory of spring web service distribution and in <Unzip_Dir>\spring-framework-3.1.0.M1\dist directory of spring framework distribution. Exact location may change depending on distribution but can be easily located. These jars need to be copied in project we will create in next step and also while deploying in server.

Step #2 In eclipse create a dynamic web project "SpringWS" with all default provided by eclipse.

Step #3 Finalize the contract (interface and message exchange structure).

We do not have to create a WSDL, this will be generated by Spring using the XSD schema and configuration (will be discussed later).

For creating the schema for request and response consider a usecase in which we will send a complex object containing some order detail in request and service will return price and other information in response.

The XSD schema will look like this:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.springwssample.org/types" xmlns:tns="http://www.springwssample.org/types" elementFormDefault="qualified">
<element name="orderRequest">
  <complexType>
    <sequence>
      <element name="item" type="string"/>
      <element name="quantity" type="integer"/>
      <element name="city" type="string"/>
      <element name="country" type="string"/>
    </sequence>
  </complexType>
</element>

<element name="orderResponse">
  <complexType>
    <sequence>
      <element name="item" type="string"/>
      <element name="quantity" type="string"/>
      <element name="city" type="string"/>
      <element name="country" type="string"/>
      <element name="price" type="double"/>
      <element name="isDeliver" type="boolean"/>
    </sequence>
  </complexType>
</element>
</schema>

Step #4 Create an endpoint to process requests and provide response. This is the implementation for the web service.

Create a Java class "OrderEndpoint" in the project created earlier in package "springws.usecase". No need to extend any class or implement any interface.

Annotate the class with @Endpoint annotation. This will make the class able to process XML messages and will be available for Spring component scanning.

Create a method "processOrder" to process order request. Annotate it with @PayloadRoot to tell Spring that this method can handle XML requests of type specified by parameters of the annotation.

@PayloadRoot(localPart = "echoRequest", namespace = "http://www.springwssample.org/types")
@ResponsePayload
public Element processOrder(@RequestPayload Element request){

}

In the method body write the processing need. This may require database access or call to some other services or some validation and may throw an exception. For simplicity we will make dummy implementation. Here is the complete class:

package springws.usecase;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

@Endpoint
public class OrderEndpoint {

    private static final String NAMESPACE = "http://www.springwssample.org/types";
   
    @PayloadRoot(localPart = "orderRequest", namespace = "http://www.springwssample.org/types")
    @ResponsePayload
    public Element processOrder(@RequestPayload Element request) throws ParserConfigurationException {
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element response = document.createElementNS(NAMESPACE, "orderResponse");
        response.appendChild(addElementWithValue(document, "item", "Item1"));
        response.appendChild(addElementWithValue(document, "quantity", "2"));
        response.appendChild(addElementWithValue(document, "city", "Ahmedabad"));
        response.appendChild(addElementWithValue(document, "country", "India"));
        response.appendChild(addElementWithValue(document, "price", "200.00"));
        response.appendChild(addElementWithValue(document, "isDeliver", "true"));
        return response;
    }
   
    private Element addElementWithValue(Document document, String element, String value){
        Element child = document.createElementNS(NAMESPACE, element);
        child.appendChild(document.createTextNode(value));
        return child;
    }
}

Step #5 AddMessageDispatcherServlet mapping in web.xml and define all incomming request mapping to this  servlet. Make this entry in web.xml as:

<display-name>My Service</display-name>
    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <init-param>
            <param-name>transformWsdlLocations</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

Step #6 Create Spring WS specific configuration for this servlet which contains dynamic WSDL generation configuration, interceptors, Spring WS specific beans etc. Create a spring-ws-servlet.xml (<servlet name>-servlet.xml) file as:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sws="http://www.springframework.org/schema/web-services"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <description>
        This web application context contains Spring-WS beans. The beans defined in this context are automatically
        detected by Spring-WS, similar to the way Controllers are picked up in Spring Web MVC.
    </description>

    <context:component-scan base-package="springws.usecase"/>

    <sws:annotation-driven />

    <sws:interceptors>
        <bean class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
            <description>
                This interceptor validates both incoming and outgoing message contents according to the 'echo.xsd' XML
                Schema file.
            </description>
            <property name="schema" value="/WEB-INF/service.xsd"/>
            <property name="validateRequest" value="true"/>
            <property name="validateResponse" value="true"/>
        </bean>
        <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
            <description>
                This interceptor logs the message payload.
            </description>
        </bean>
    </sws:interceptors>

    <sws:dynamic-wsdl id="order" portTypeName="Order" locationUri="orderservices">
        <sws:xsd location="/WEB-INF/service.xsd"/>
    </sws:dynamic-wsdl>
</beans>


The dynamic WSDL configuration:

    <sws:dynamic-wsdl id="order" portTypeName="Echo" locationUri="orderservices">
        <sws:xsd location="/WEB-INF/service.xsd"/>
    </sws:dynamic-wsdl>

 will publish the WSDL at http://<host>:<port>/<web app context>/orderservices/order.wsdl.
To enable this to work on different app context "transformWsdlLocations" init param is added to the servlet.


 Step #7 Build project and deploy in tomcat server.
The project structure in eclipse is like this:

An automated build script to create .war can be created to build project or for simplicity just create a directory "classes" under "WEB-INF" directory and add compiled class file OrderEndpoint.class into it. The class file can be found in build directory. Copy the content of "build/classes" directory into classes folder.

Then create a .war file from the contents of "WebContent" directory or just copy this directory into Tomcat's "webapps" directory.

You need to put following jars in WEB-INF/lib directory :

Additionally put commons logging and log4j jars.

Start tomcat and browse http://localhost:8080/WebContent/orderservices/order.wsdl to see the WSDL published.

Step #8 Create a Web Service Client for order service using WebServiceTemplate.

You can create a new project or can use the project created earlier. For simplicity I am using same project.
Create a class Client.java in springws.usecase package and put the following code in it:

package springws.usecase;


import java.io.StringReader;

import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.ws.client.core.WebServiceTemplate;

public class Client {



//sample request XML
    private static String MESSAGE =
        "<orderRequest xmlns=\"http://www.springwssample.org/types\"><item>Spring Flowers</item><quantity>2</quantity><city>Ahmedabad</city><country>India</country></orderRequest>";

    public static void main(String[] args){
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);

//WebServiceTemplate provides the functionality for sending and receiving webservice messages.
        WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
        webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/WebContent/orderservices",
                source, result);

    }
}

Run this client and following message will appear on console:

<?xml version="1.0" encoding="UTF-8"?><orderResponse xmlns="http://www.springwssample.org/types"><item>Item1</item><quantity>2</quantity><city>Ahmedabad</city><country>India</country><price>200.00</price><isDeliver>true</isDeliver></orderResponse>


In this example client has hard-coded messages, but if in an application where it is created dynamically we may need to process message before sending. For this we can use WebServiceTemplate#sendSourceAndReceiveToResult method with WebServiceMessageCallback as argument. This class has one method
public void doWithMessage(WebServiceMessage arg0)    throws IOException, TransformerException
that need to be implemented and gives handle to the WebSeviceMessage before sending.


See the response and request, as we are sending and receiving raw XML we can choose our own marshalling/unmarshalling technology like JAXB, XStream, JDOM, DOM4J or Axiom. In the future posts I will explain how to use Axiom to create message and handle response received. As Axiom is based on pull parsing so it is efficient for handling large XML messages. This is developed along with Axis2.

Troubleshooting:

 org.springframework.ws.client.WebServiceTransportException: Not Found [404]
    at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:663)
    at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:587)
    at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
    at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
    at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
    at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:421)    

This error may come due to mismatching namespaces decleration or localpart (element name) in WSDL (derived from XSD schema) and in Endpoint class or request sent from client. Check that the payload root method in endpoint for localpart and namespaceuri used these should be element name for request message in schema and namespace of the schema. Also, ensure request sent from client is in same namespace.

org.springframework.ws.soap.client.SoapFaultClientException: Validation error
    at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:37)
    at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:774)
    at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:600)
    at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
    at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
    at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
    at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:421)

This error is due to request sent or response sent message is not valid XML according to schema. This validation is done by interceptors registered in spring-ws-servlet.xml.


Thats it! we have created a web service and a sample client for it. For any issues with this post contact me at sylentprayer@gmail.com or leave a comment.

Refrences:
Spring WS reference guide.

Get code as eclipse project from here.

Popular Posts