Wednesday, September 29, 2010

Building a web service with Spring-WS

[WCF client for a Spring Webservice: An interoperabilty story. Part 2.]

     There is an abundance of web service frameworks in Java. Apache Axis(1&2), Apache CXF, XFire, Metro (from the Java family itself),.. the list goes on. All are rich in features. It is not an easy job to select the right one always.

    Our selection of Spring Web Services(Spring-WS) was based on few compelling reasons.
  • First and foremost reason was its coverage. Spring covers almost all areas of Web Service specifications, utilizing the existing libraries wherever they are strong. Spring allows you to plug in the best libraries available for each specific need. For example, we can choose between SAAJ(SOAP with Attachments API) and Axis 2's AXIOM for the concrete implementations of SOAP Message Factories, to create the SOAP Messages.
  • It supports most of the XML APIs for handling the incoming XML messages, ranging from JAXP APIs such as DOM, SAX, and StAX, but also JDOM, dom4j, XOM, or even marshalling technologies. XML marshalling technologies include JAXB 1 and 2, Castor, XMLBeans, JiBX, and XStream.
  • The highly configurable WS-Security implementation of Spring-WS allows you to sign SOAP messages, encrypt and decrypt them, or authenticate against them.     Primarily WSS4J is used, while you can leverage your existing Acegi based Spring-Security as well.
  • Moreover, we were already quiet comfortable with the Spring style of application development. Our Spring expertise enabled us to jump start into the implementation. (For those who are new to Spring Framework, I highly recommend to dive into it, you will soon start enjoying your programming because of this wonderful framework).

    For the purpose of illustrating the construction process of web services, let us use a simple business scenario of a fictitious restaurant, Live Restaurant , that 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).
  1. placeOrder
  2. cancelOrder
 
 Figure 2.1. OrderService Java Interface


    Now we need to publish OrderService as a web service. Let us examine how we will accomplish it.

    When building web services, generally there are two different approaches considered – Contract First(WSDL/XSD first) and Contract Last(Java first). Spring recommends and only supports the Contract First approach. The Spring team describes the reasons for that decision in this link. http://static.springsource.org/spring-ws/sites/1.5/reference/html/why-contract-first.html.

    So, in our implementation, we start with an XML Schema(XSD), as our contract. Spring, with its magical components generates rest of the artifacts to form the complete WSDL.

    Now let us look at the steps involved in building the service.

    Let us use our favorite Eclipse IDE for Java EE Developers. Additionally, you will need the JAXB plugin for Eclipse. We will use the Apache Tomcat Server 6 for deploying the service.

Now let us follow these steps.
  1. Add the JAXB Plugin for Eclipse.
    1. Download the JAXB Eclipse plugin from
      https://jaxb-workshop.dev.java.net/plugins/eclipse/xjc-plugin.html%20

      OR

      https://jaxb-workshop.dev.java.net/servlets/ProjectDocumentList?folderID=4962&expandFolder=4962&folderID=0 .

       
    2. Copy the extracted plugin into the plugins folder of your eclipse installation and then restart eclipse. Now we are ready to go. We are using JAXB(Java API for XML Binding) for the XML marshalling. The JAXB plugin will generate the Java (model) classes for us from a given XSD file.
       
  2. Create a Dynamic Web Project(Name: LiveRestaurant) in Eclipse, with src and resource folders as Source Folders on build path(Project --> Properties --> Java Build Path --> Source).
    You should have the following project structure when the setup is complete.




    Figure 2.2 Project Structure.


  3. Now we need to write the XML Schema first, which is based on this domain model.


    Figure 2.3. Domain Model


    Here is the XML Schema file, OrderService.xsd, save it under resource/com/live/order/schema

    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.liverestaurant.com/OrderService/schema"
        xmlns:tns="http://www.liverestaurant.com/OrderService/schema"
        elementFormDefault="qualified"
        xmlns:QOrder="http://www.liverestaurant.com/OrderService/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>

        <element name="cancelOrderRequest">
            <complexType>
                <sequence>
                    <element name="refNumber" type="string"></element>
                </sequence>
            </complexType>
        </element>

        <element name="cancelOrderResponse">
            <complexType>
                <sequence>
                    <element name="cancelled" type="boolean"></element>
                </sequence>
            </complexType>
        </element>

      <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>
      
      <complexType name="Name">
          <sequence>
              <element name="fName" type="string"></element>
              <element name="mName" type="string"></element>
              <element name="lName" type="string"></element>
          </sequence>
      </complexType>

      <complexType name="Address">
          <sequence>
              <element name="doorNo" type="string"></element>
              <element name="building" type="string"></element>
              <element name="street" type="string"></element>
              <element name="city" type="string"></element>
              <element name="country" type="string"></element>
              <element name="phoneMobile" type="string"></element>
              <element name="phoneLandLine" type="string"></element>
              <element name="email" type="string"></element>
          </sequence>
      </complexType>

        <simpleType name="FoodItemType">
            <restriction base="string">
                <enumeration value="Snacks"></enumeration>
                <enumeration value="Beverages"></enumeration>
                <enumeration value="Starters"></enumeration>
                <enumeration value="Meals"></enumeration>
                <enumeration value="Coffee"></enumeration>
                <enumeration value="Juices"></enumeration>
                <enumeration value="Desserts"></enumeration>
            </restriction>
        </simpleType>

        <complexType name="FoodItem">
            <sequence>
                <element name="type" type="QOrder:FoodItemType"></element>
                <element name="name" type="string"></element>
                <element name="quantity" type="double"></element>
            </sequence>
        </complexType>
    </schema>
    Code 2.1. OrderService.xsd

  4. We can now use the JAXB Plugin to generate the Domain classes from this XSD file.

    1. Right click on the xsd file(OrderService.xsd)
    2. JAXB 2.1
    3. Run XJC.
    4. Enter Package name : com.live.order.domain
    5. Enter Output Directory: <Project Root>\src\com\live\order\domain
    6. Next 
    7. Finish.



      Figure 2.4. XML to Java (XJC) Wizard for JAXB

    8. Refresh your workspace and see the classes generated in the src directory.
  5. Create the OrderService Java interface in the package, src/com.live.order.service


    package com.live.order.service;

    import com.live.order.domain.Order;

    /**
    * <pre>
    * Service interface for Order Service operations, handles two operations. <ul>
    *     <li>placeOrderRequest</li>
    *     <li>cancelOrderRequest</li>
    * </ul>
    * </pre>
    *
    *
    * @see OrderServiceImpl
    *
    */
    public interface OrderService {

        String placeOrder(Order order);

        boolean cancelOrder(String orderRef);
    }
    Code 2.2.OrderService.java
  6. Create a concrete implementation for OrderService, OrderServiceImpl.java, under  src/com.live.order.service.


    package com.live.order.service;

    import java.util.Calendar;

    import org.apache.commons.lang.ObjectUtils;
    import org.apache.log4j.Logger;

    import com.live.order.domain.Order;

    /**
    * <pre>
    * Service implementation for {@link OrderService}
    * </pre>
    *
    * @see OrderService
    *
    */
    public class OrderServiceImpl implements OrderService {
       
        private static final Logger logger = Logger.getLogger(OrderServiceImpl.class);

        public OrderServiceImpl() {
        }

        @Override
        public String placeOrder(Order order) {
            logger.info("Order has been placed. Order Info is : " + ObjectUtils.toString(order));
            return getRandomOrderRefNo();
        }

        @Override
        public boolean cancelOrder(String orderRef) {
            logger.info("Order has been placed. Order Reference : " + orderRef);
            return true;
        }
       
        private String getRandomOrderRefNo() {
            Calendar calendar = Calendar.getInstance();
            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH);
            int day = calendar.get(Calendar.DAY_OF_MONTH);

            return "Ref-" + year + "-" + month + "-" + day + "-" + Math.random();
           
        }
    }

    Code 2.3.OrderServiceImpl.java
  7. Now, create an Endpoint class for exposing this service, internally delegating to the OrderServiceImpl.

    package com.live.order.service.endpoint;

    import javax.xml.bind.JAXBElement;
    import javax.xml.namespace.QName;

    import org.springframework.ws.server.endpoint.annotation.Endpoint;
    import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

    import com.live.order.domain.CancelOrderRequest;
    import com.live.order.domain.CancelOrderResponse;
    import com.live.order.domain.ObjectFactory;
    import com.live.order.domain.PlaceOrderRequest;
    import com.live.order.domain.PlaceOrderResponse;
    import com.live.order.service.OrderService;

    /**
    * <pre>
    * This is the endpoint for the {@link OrderService}.
    * Requests are simply delegated to the {@link OrderService} for processing.
    * Two operations are mapped, using annotation, as specified in the link,
    * <a href="http://static.springsource.org/spring-ws/sites/1.5/reference/html/server.html#server-at-endpoint"
    * >http://static.springsource.org/spring-ws/sites/1.5/reference/html/server.html#server-at-endpoint</a
    * ><ul>
    *     <li>placeOrderRequest</li>
    *     <li>cancelOrderRequest</li>
    * </ul>
    * </pre>
    *
    */
    @Endpoint
    public class OrderServicePayloadRootAnnotationEndPoint {

        private final OrderService orderService;
        private final ObjectFactory JAXB_OBJECT_FACTORY = new ObjectFactory();
       
        public OrderServicePayloadRootAnnotationEndPoint(OrderService orderService) {
            this.orderService = orderService;
        }

        @PayloadRoot(localPart = "placeOrderRequest", namespace = "http://www.liverestaurant.com/OrderService/schema")
        public JAXBElement<PlaceOrderResponse> getOrder(
                PlaceOrderRequest placeOrderRequest) {
            PlaceOrderResponse response = JAXB_OBJECT_FACTORY
                    .createPlaceOrderResponse();
            response.setRefNumber(orderService.placeOrder(placeOrderRequest
                    .getOrder()));

            return new JAXBElement<PlaceOrderResponse>(new QName(
                    "http://www.liverestaurant.com/OrderService/schema",
                    "placeOrderResponse"), PlaceOrderResponse.class, response);
        }

        @PayloadRoot(localPart = "cancelOrderRequest", namespace = "http://www.liverestaurant.com/OrderService/schema")
        public JAXBElement<CancelOrderResponse> cancelOrder(
                CancelOrderRequest cancelOrderRequest) {
            CancelOrderResponse response = JAXB_OBJECT_FACTORY
                    .createCancelOrderResponse();
            response.setCancelled(orderService.cancelOrder(cancelOrderRequest
                    .getRefNumber()));
            return new JAXBElement<CancelOrderResponse>(new QName(
                    "http://www.liverestaurant.com/OrderService/schema",
                    "cancelOrderResponse"), CancelOrderResponse.class, response);
        }

    }
    Code 2.4. OrderServicePayloadRootAnnotationEndPoint.java
  8. Add Spring-WS nature and dependencies to the project
    1. Download Spring-WS-2.0.0-M2-with-dependencies.zip from http://www.springsource.com/download/community.
    2. Extract and copy the dependency jar files to <project-root>/WebComtent/WEB-INF/lib.

  9. Modify the web configuration file, web.xml to add the MessageDispatcherServlet as given below(the entire web.xml file)



    <?xml version="1.0" encoding="UTF-8"?>
               
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
        xmlns="http://java.sun.com/xml/ns/javaee"          xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
               
        <display-name>LiveRestaurant</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>
    Code 2.3. web.xml

     
  10. Add the Spring-WS configuration file, under WEB-INF, with the following content


    <?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:p="http://www.springframework.org/schema/p"                     xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans             http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context                        
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

        <!--     PayloadRootAnnotationMethodEndpointMapping is the Mapping that             detects and handles the @PayloadRoot Annotation -->
        <bean             class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
            <property name="interceptors">
                <list>
                     <bean             class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
                </list>
            </property>
        </bean>
               
        <bean id="orderServiceEndpoint"
    class="com.live.order.service.endpoint.OrderServicePayloadRootAnnotationEndPoint">
            <constructor-arg>
                <bean class="com.live.order.service.OrderServiceImpl"/>
            </constructor-arg>
        </bean>
        <bean id="OrderService"             class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
            <property name="schema" ref="orderServiceSchema"/>
            <property name="portTypeName" value="OrderService"/>
            <property name="locationUri" value="http://www.liverestaurant.com/OrderService/" />
            <property name="targetNamespace" value="http://www.liverestaurant.com/OrderService/schema"/>
        </bean>
               
        <bean id="orderServiceSchema" class="org.springframework.xml.xsd.SimpleXsdSchema">
            <property name="xsd" value="/WEB-INF/classes/com/live/order/schema/OrderService.xsd"/>
        </bean>

        <bean             class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
            <constructor-arg ref="marshaller" />
        </bean>

        <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
            <property name="contextPath" value="com.live.order.domain"/>
        </bean>
               
    </beans>       
    Code 2.4. spring-ws-servlet.xml

  11. Configure your Tomcat Server inside your Eclipse workspace.
  12. Deploy the application into the Tomcat Server
  13. Resolve all IDE specific and deployment errors by resolving dependencies and paths
  14. Find the WSDL url at http://localhost:8080/LiveRestaurant/spring-ws/OrderService.wsdl.

    You should be able to see the wsdl with this structure, in the usual xml format.


    <?xml version="1.0" encoding="UTF-8"?>
    <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:sch="http://www.liverestaurant.com/OrderService/schema"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.liverestaurant.com/OrderService/schema"
        targetNamespace="http://www.liverestaurant.com/OrderService/schema">
        <wsdl:types>
            <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:QOrder="http://www.liverestaurant.com/OrderService/schema"
                elementFormDefault="qualified"
                targetNamespace="http://www.liverestaurant.com/OrderService/schema"
                xmlns:tns="http://www.liverestaurant.com/OrderService/schema">

                <element name="placeOrderRequest">
                    <complexType>
                        <sequence>
                            <element name="order" type="QOrder:Order"/>
                        </sequence>
                    </complexType>
                </element>

                <element name="placeOrderResponse">
                    <complexType>
                        <sequence>
                            <element name="refNumber" type="string" />
                        </sequence>
                    </complexType>
                </element>

                <element name="cancelOrderRequest">
                    <complexType>
                        <sequence>
                            <element name="refNumber" type="string" />
                        </sequence>
                    </complexType>
                </element>

                <element name="cancelOrderResponse">
                    <complexType>
                        <sequence>
                            <element name="cancelled" type="boolean"/>
                        </sequence>
                    </complexType>
                </element>

                <complexType name="Order">
                    <sequence>
                        <element name="refNumber" type="string" />
                        <element name="customer" type="QOrder:Customer"/>
                        <element name="dateSubmitted" type="dateTime" />
                        <element name="orderDate" type="dateTime" />
                        <element maxOccurs="unbounded" minOccurs="1" name="items"
                            type="QOrder:FoodItem">
                        </element>
                    </sequence>
                </complexType>

                <complexType name="Customer">
                    <sequence>
                        <element name="addressPrimary" type="QOrder:Address" />
                        <element name="addressSecondary" type="QOrder:Address" />
                        <element name="name" type="QOrder:Name" />
                    </sequence>
                </complexType>

                <complexType name="Name">
                    <sequence>
                        <element name="fName" type="string" />
                        <element name="mName" type="string" />
                        <element name="lName" type="string" />
                    </sequence>
                </complexType>

                <complexType name="Address">
                    <sequence>
                        <element name="doorNo" type="string" />
                        <element name="building" type="string" />
                        <element name="street" type="string" />
                        <element name="city" type="string" />
                        <element name="country" type="string" />
                        <element name="phoneMobile" type="string" />
                        <element name="phoneLandLine" type="string" />
                        <element name="email" type="string" />
                    </sequence>
                </complexType>

                <simpleType name="FoodItemType">
                    <restriction base="string">
                        <enumeration value="Snacks" />
                        <enumeration value="Beverages" />
                        <enumeration value="Starters" />
                        <enumeration value="Meals" />
                        <enumeration value="Coffee" />
                        <enumeration value="Juices" />
                        <enumeration value="Desserts" />
                    </restriction>
                </simpleType>

                <complexType name="FoodItem">
                    <sequence>
                        <element name="type" type="QOrder:FoodItemType"/>
                        <element name="name" type="string" />
                        <element name="quantity" type="double" />
                    </sequence>
                </complexType>


            </schema>
        </wsdl:types>
        <wsdl:message name="placeOrderRequest">
            <wsdl:part element="tns:placeOrderRequest" name="placeOrderRequest">
            </wsdl:part>
        </wsdl:message>
        <wsdl:message name="cancelOrderResponse">
            <wsdl:part element="tns:cancelOrderResponse" name="cancelOrderResponse">
            </wsdl:part>
        </wsdl:message>
        <wsdl:message name="cancelOrderRequest">
            <wsdl:part element="tns:cancelOrderRequest" name="cancelOrderRequest">
            </wsdl:part>
        </wsdl:message>
        <wsdl:message name="placeOrderResponse">
            <wsdl:part element="tns:placeOrderResponse" name="placeOrderResponse">
            </wsdl:part>
        </wsdl:message>
        <wsdl:portType name="OrderService">
            <wsdl:operation name="placeOrder">
                <wsdl:input message="tns:placeOrderRequest" name="placeOrderRequest">
                </wsdl:input>
                <wsdl:output message="tns:placeOrderResponse" name="placeOrderResponse">
                </wsdl:output>
            </wsdl:operation>
            <wsdl:operation name="cancelOrder">
                <wsdl:input message="tns:cancelOrderRequest" name="cancelOrderRequest">
                </wsdl:input>
                <wsdl:output message="tns:cancelOrderResponse" name="cancelOrderResponse">
                </wsdl:output>
            </wsdl:operation>
        </wsdl:portType>
        <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://www.liverestaurant.com/OrderService/" />
            </wsdl:port>
        </wsdl:service>
    </wsdl:definitions>
    OrderService.wsdl
          Now our web-service is successfully deployed, ready to be consumed by any client application which has an access to the WSDL. Note, the URL for a client program is http://localhost:8080/LiveRestaurant/spring-ws/OrderService.

         In the next post, I will describe how these components work together, and the roles of each component in that process, before we build a client program for this service. Expect it soon...