Saturday, July 7, 2007

Step by step Spring-WS

Took a look at Spring-WS and came up with a quick example service to describe its use. I decided to build an 'echo' service. Send in a text and it will echo that back with a date and time appended to the text.

After building the application I saw that Spring-WS comes with a sample echo service application. Oh well. Since I put in the effort here is the article on it.

Spring-WS encourages document based web services. As you know there are mainly two types of web services:

  • RPC based. 
  • Document based.
In RPC you think in terms of traditional functional programming. You decide what operations you want and then use the WSDL to describe those operations and then implement them. If you look at any RPC based WSDL you will see in the binding section the various operations (or methods).

In the document based approach you no longer think of operations (their parameters and return types). You decide on what XML document you want to send in as input and what XML document you want to return from your web service as a response.

When you think document based the traditional approach thus far has been to draw up the WSDL and then go from there. I see no problem in this approach.

Spring-WS encourages a more practical approach to designing document based web services. Rather than think WSDL, it pushes you to think XSD (or the document schema) and then Spring-WS can auto-generate the WSDL from the schema.

Lets break it up into simpler steps:
  1. Create your XML schema (.xsd file). Inside the schema you will create your request messages and response messages. Bring up your favourite schema editor to create the schema or write sample request and response XML and then reverse-engineer the schema (check if your tool supports it).
  2. You have shifted the focus onto the document (or the XML). Now use Spring-WS to point to the XSD and set up a few Spring managed beans and soon you have the web service ready. No WSDL was ever written.
Spring-WS calls this the contract-first approach to building web services.

Lets see the echo service in action. You will notice that I do not create any WSDL document throughout this article.

Business Case:

Echo service takes in an XML request document and returns an XML document with a response. The response contains the text that was sent in, appended with a timestamp.


Request XML Sample:
 <ec:EchoRequest>
  <ec::Echo>
   <ec:Name>Mathew</ec:Name>
  </ec:Echo>
 </ec:EchoRequest>

The schema XSD file for this can be found in the WEB-INF folder of the application (echo.xsd).
Response XML Sample:
<ec:EchoResponse>
 <ec:Message>echo back: name Mathew received on 05-06-2007 06:42:08 PM
 </ec:Message>
</ec:EchoResponse>
The schema XSD file for this can be found in the WEB-INF folder of the application (echo.xsd).

If you inspect the SOAP request and response you will see that this XML is whats inside the SOAP body. Thats precisely what is document based web services.

Echo Service Implementation:

Here is the echo service Java interface and its related implementation. As you can see this is a simple POJO.
package echo.service;

public interface EchoService {
    public String echo(java.lang.String name);
}
package echo.service;

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class EchoServiceImpl implements EchoService {

    public String echo(String name) {
        if (name == null || name.trim().length() == 0) {
            return "echo back: -please provide a name-";
        }
        SimpleDateFormat dtfmt = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a");
        return "echo back: name " + name + " received on "
                + dtfmt.format(Calendar.getInstance().getTime());
    }
}

Now the Spring-WS stuff:


Here is the web.xml for the sake of clarity.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee">

    <display-name>Echo Web Service Application</display-name>

    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

Only thing to note in the web.xml is the Spring-WS servlet.

Next is the all important Spring bean configuration XML.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans">

    <bean id="echoEndpoint" class="echo.endpoint.EchoEndpoint">
        <property name="echoService"><ref bean="echoService"/></property>
    </bean>

    <bean id="echoService" class="echo.service.EchoServiceImpl"/>

    <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
        <property name="mappings">
            <props>
                <prop key="{http://www.averconsulting.com/echo/schemas}EchoRequest"
                >echoEndpoint</prop>
            </props>
        </property>
        <property name="interceptors">
            <bean
                class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"
            />
        </property>
    </bean>

    <bean id="echo" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
        <property name="builder">
            <bean
                class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
                <property name="schema" value="/WEB-INF/echo.xsd"/>
                <property name="portTypeName" value="Echo"/>
                <property name="locationUri" value="http://localhost:9090/echoservice/"/>
            </bean>
        </property>
    </bean>
</beans>
  • Registered the 'echoService' implementation bean.
  • Registered an endpoint class named 'echoEndpoint'. The endpoint is the class that receives the incoming web service request. 
  • The endpoint receives the XML document. You parse the XML data and then call our echo service implementation bean.
  • The bean 'PayloadRootQNameEndpointMapping' is what maps the incoming request to the endpoint class. Here we set up one mapping. Anytime we see a 'EchoRequest' tag with the specified namespace we direct it to our endpoint class.
  • The 'XsdBasedSoap11Wsdl4jDefinitionBuilder' class is what does the magic of converting the schema XSD to a WSDL document for outside consumption. Based on simple naming conventions in the schema (like XXRequest and XXResponse) the bean can generate a WSDL. This rounds up the 'thinking in XSD for document web services' implementation approach.  Once deployed the WSDL is available at http://localhost:9090/echoservice/echo.wsdl.
Finally here is the endpoint class. This is the class, as previously stated, that gets the request XML and can handle the request from there.
package echo.endpoint;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint;

import echo.service.EchoService;

public class EchoEndpoint extends AbstractJDomPayloadEndpoint {
    private EchoService echoService;

    public void setEchoService(EchoService echoService) {
        this.echoService = echoService;
    }

    protected Element invokeInternal(Element request) throws Exception {
        // ok now we have the XML document from the web service request
        // lets system.out the XML so we can see it on the console (log4j
        // latter)
        System.out.println("XML Doc >> ");
        XMLOutputter xmlOutputter = new XMLOutputter();
        xmlOutputter.output(request, System.out);

        // I am using JDOM for my example....feel free to process the XML in
        // whatever way you best deem right (jaxb, castor, sax, etc.)

        // some jdom stuff to read the document
        Namespace namespace = Namespace.getNamespace("ec",
                "http://www.averconsulting.com/echo/schemas");
        XPath nameExpression = XPath.newInstance("//ec:Name");
        nameExpression.addNamespace(namespace);

        // lets call a backend service to process the contents of the XML
        // document
        String name = nameExpression.valueOf(request);
        String msg = echoService.echo(name);

        // build the response XML with JDOM
        Namespace echoNamespace = Namespace.getNamespace("ec",
                "http://www.averconsulting.com/echo/schemas");
        Element root = new Element("EchoResponse", echoNamespace);
        Element echoResponse = new Element("EchoResponse", echoNamespace);
        root.addContent(echoResponse);
        Element message = new Element("Message", echoNamespace);
        echoResponse.addContent(message);
        message.setText(msg);
        Document doc = new Document(root);

        // return response XML
        System.out.println();
        System.out.println("XML Response Doc >> ");
        xmlOutputter.output(doc, System.out);
        return doc.getRootElement();
    }
}
This is a simple class. Important point to note is that it extends 'AbstractJDomPayloadEndpoint'. The 'AbstractJDomPayloadEndpoint' class is a helper that gives you the XML payload as a JDom object. There are similar classes built for SAX, Stax and others. Most of the code above is reading the request XML using JDOM API and parsing the data out so that we may provide it to our echo service for consumption.

Finally I build a response XML document to return and thats it.

Download the sample Application:
Click here to download the jar file containing the application. The application is built using Maven. If you do not have Maven please install it. Once Maven is installed run the following commands:
  1. mvn package (this will generate the web service war file in the target folder).
  2. mvn jetty:run (this will bring up Jetty and you can access the wsdl at http://localhost:9090/echoservice/echo.wsdl.
  3. Finally use some web service accessing tool like the eclipse plug-in soapUI to invoke the web service.
As you can see this is relatively simple. Spring-WS supports the WS-I basic profile and WS-Security. I hope to look at the WS-Security support sometime soon. Also interesting to me is the content based routing feature. This lets you configure which object gets the document based on the request XML content. We did the QName based routing in our example but I would think the content based parsing is of great interest.

While I could not find a roadmap for Spring-WS, depending on the features it starts supporting this could become a very suitable candidate for web service integration projects. Sure folks will say where is WS-Transactions and all of that, but tell me how many others implement that. I think if Spring-WS grows to support 90% of what folks need in integration projects then it will suffice. I hope in future I see some support for content transformation.

4 comments:

Anonymous said...

clearly explaining things.. good one

Anonymous said...

the best example fro spring-ws on the net... thx

Ben Northrop said...

great post. thanks much for the help

Anonymous said...

Just a question..The namespace here can be anything other than link right?

Namespace namespace = Namespace.getNamespace("ec",
"http://www.averconsulting.com/echo/schemas");
XPath nameExpression = XPath.newInstance("//ec:Name");
nameExpression.addNamespace(namespace);