Thursday, November 4, 2010

Web Service Client with Spring-WS



Spring-WS fares beautifully at the client-side as well. With its simple API for client side web service, Spring-ws simplifies web service client programming. This post demonstrates how easy it is to develop a web service client using Spring-WS.

WebServiceTemplate, the convenient helper class provided by Spring-WS, provides a set of easy to use helper methods to perform common operations, plus the callback methods for more sophisticated operations, similar to the other familiar Spring template classes.

You may take a quick look at the Spring-WS documentation for an overview and the useful methods that the WebServiceTemplate offers. The usage is straight forward, hence no scope further explanation. 

The ws-client program given here is exactly a Java version of the one developed in the C# language[.Net framework] with the WCF technology, in the part 4 of this series. A client API is developed implementing the OrderService.java (the service interface) as a proxy. Then it is springified in the ApplicationContext.xml, along with the other required components to make the web service wiring. Instead of a Form as in the part 4, here it is a JUnit Testcase, which calls the API. 

Here is the summary of tools and methods used in this program.

IDESpringsource Tool Suite(STS), based on Eclipse (Download from here)    
Transport MechanismHTTP
Message FactorySAAJSoapMessageFactory (SAAJ-specific implementation of WebserviceMessageFactory)
XML Marshaling TechnologyJAXB 2   

Now, let us take a look at the simple steps involved in building a Web Service Client program for our OrderService which was built under the part 2 of this series[WCF client for a Spring Web service: An interoperability story].

  1. Prepare the workspace

    1. Create New Java Project[name:LiveRestautantClient] in STS(Springsource Tool Suite)/Eclipse.

    2. Add all the Spring-ws libraries as dependencies to the project.

    3. Create three source folders, src, test and resource.

    4. Add the project, LiveRestaurant as a project dependency to this project (This step is to reuse the OrderService.java (Java Interface) as the base specification for the OrderServiceClient, which is described below. Alternatively, you can copy just that file and keep locally).

    5. Download Jaxb2 XJC Plugin from         https://jaxb-workshop.dev.java.net/servlets/ProjectDocumentList?folderID=4962&expandFolder=4962&folderID=4962 and add to the IDE.  
       
  2. Generate the model(data-types) POJOs from the XSD. (If you have the LiveRestaurant Project, where the web service is built in your work space, you can just add that project to this client project as a dependency, and skip this entire step)

    1. Copy the OrderService.xsd from the LiveRestaurant service project to the resource folder

    2. Right click on the XSD file >> JAXB 2.0 >> Run XJC.



    3. Provide package name: com.live.order.domain

    4. Output Directory: LiveRestaurantClient\src

    5. Click Finish.

    6. Refresh the project and make sure the classes are generated under src.

  3. Write the client side API, OrderServiceClient as a proxy for the OrderService under the source folder, src, inside package, com.live.order.service.client. Here is a sample code that you can reuse. WebServiceTemplate is used here, to invoke the operations.


    package com.live.order.service.client;

    import org.apache.log4j.Logger;

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

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

    public class OrderServiceClient implements OrderService {

        private static final Logger logger = Logger.getLogger(OrderServiceClient.class);
        private static final ObjectFactory WS_CLIENT_FACTORY = new     ObjectFactory();
               
        private  WebServiceTemplate webServiceTemplate;

        public OrderServiceClient(WebServiceTemplate webServiceTemplate) {
            this.webServiceTemplate = webServiceTemplate;
        }
               
        @Override
        public boolean cancelOrder(String orderRef) {
            logger.debug("Preparing CancelOrderRequest.....");
            CancelOrderRequest request =   WS_CLIENT_FACTORY.createCancelOrderRequest();
            request.setRefNumber(orderRef);

            logger.debug("Invoking Web service Operation[CancelOrder]....");
            CancelOrderResponse response = (CancelOrderResponse) webServiceTemplate.marshalSendAndReceive(request);
               
            logger.debug("Has the order cancelled: " + response.isCancelled());
               
            return response.isCancelled();
        }

        @Override
        public String placeOrder(Order order) {
            logger.debug("Preparing PlaceOrderRequest.....");
                    PlaceOrderRequest request = WS_CLIENT_FACTORY.createPlaceOrderRequest();
                    request.setOrder(order);
               
            logger.debug("Invoking Web service Operation[PlaceOrder]....");
                    PlaceOrderResponse response = (PlaceOrderResponse) webServiceTemplate.marshalSendAndReceive(request);
            logger.debug("Order reference:

    "
    + response.getRefNumber());
            return response.getRefNumber();
        }
    }   
    OrderServiceClient.java


  4. Create the Spring ApplicationContext file wiring the components.



    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/util
            http://www.springframework.org/schema/util/spring-util-2.0.xsd">
           
        <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
            <property name="soapVersion">
                <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_11" />
            </property>
        </bean>
           
        <bean id="orderServiceMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
            <property name="contextPath" value="com.live.order.domain" />
        </bean>
       
        <bean id="orderServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
            <constructor-arg ref="messageFactory" />
            <property name="marshaller" ref="orderServiceMarshaller"></property>
            <property name="unmarshaller" ref="orderServiceMarshaller"></property>
            <property name="messageSender">
                <bean
                    class="org.springframework.ws.transport.http.CommonsHttpMessageSender">
                </bean>
            </property>
            <property name="defaultUri" value="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" />
        </bean>
       
        <bean id="OrderServiceClient" class="com.live.order.service.client.OrderServiceClient">
            <constructor-arg ref="orderServiceTemplate"></constructor-arg>
        </bean>
    </beans>
    ApplicationContext.xml


  5. OrderServiceClientTest - A Junit Testcase, which acts as the client program. It loads the spring ApplicationContext while startup to take hold of the live Spring-WS components. OrderServiceClientTest primarily invokes the client API with some dummy values, and just logs the response.


    package com.live.order.service.client.test;

    import static org.junit.Assert.assertNotNull;
    import static org.junit.Assert.assertTrue;

    import java.util.ArrayList;
    import java.util.Calendar;
    import java.util.GregorianCalendar;
    import java.util.List;

    import org.junit.BeforeClass;
    import org.junit.Test;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

    import com.live.order.domain.Address;
    import com.live.order.domain.Customer;
    import com.live.order.domain.FoodItem;
    import com.live.order.domain.FoodItemType;
    import com.live.order.domain.Name;
    import com.live.order.domain.Order;
    import com.live.order.service.OrderService;

    import com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl;

    public class OrderServiceClientTest {

        private static ClassPathXmlApplicationContext context = null;

        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
            context = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
        }

        @Test
        public void testCancelOrder() {
            OrderService client = (OrderService) context.getBean("OrderServiceClient");
            boolean cancelled = client.cancelOrder("Ref-2010-9-15-0.8432452204897198");
           
            assertTrue (cancelled);
        }

        @Test
        public void testPlaceOrder() {
            OrderService client = (OrderService) context.getBean("OrderServiceClient");
           
            Order order = prepareDummyOrder();
            String orderRef = client.placeOrder(order);
           
            assertNotNull(orderRef);
        }

        private Order prepareDummyOrder() {
            Order order = new Order();
            order.setCustomer(prepareCustomer());
            order.setDateSubmitted(prepareDate(2010, 10, 15, 8, 00, 00));
            order.setOrderDate(prepareDate(2010, 10, 15, 12, 00, 00));
            order.getItems().addAll(prepareOrderItems());
            return order;
        }

        private List<FoodItem> prepareOrderItems() {
            List<FoodItem> items = new ArrayList<FoodItem>(5);
            items.add(prepareFoodItem("Vegetable Thali",

    FoodItemType.
    MEALS, 2));
            items.add(prepareFoodItem("Kheer/ Palpayasam",

    FoodItemType.
    DESSERTS, 4));
            items.add(prepareFoodItem("Fresh Orange Juice",

    FoodItemType.
    JUICES, 1));
            items.add(prepareFoodItem("Fresh Carrot Juice",

    FoodItemType.
    JUICES, 1));
            items.add(prepareFoodItem("Sweet Corn Soup",

    FoodItemType.
    STARTERS, 2));
            return items;
        }

        private XMLGregorianCalendarImpl prepareDate(int year, int month, int date, int hour, int minute, int second) {
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.set(Calendar.YEAR, year);
            calendar.set(Calendar.MONTH, month);
            calendar.set(Calendar.DAY_OF_MONTH, date);
            calendar.set(Calendar.HOUR_OF_DAY, hour);
            calendar.set(Calendar.MINUTE, minute);
            calendar.set(Calendar.SECOND, second);
            return new XMLGregorianCalendarImpl(calendar);
        }

        private FoodItem prepareFoodItem(String name, FoodItemType type,
                double quantity) {
            FoodItem item = new FoodItem();
            item.setName(name);
            item.setType(type);
            item.setQuantity(quantity);
            return item;
        }

        private Customer prepareCustomer() {
            Customer customer = new Customer();
            customer.setName(prepareCustomerName());
            customer.setAddressPrimary(prepareAddress("123", "My Office Building",
                    "My Office Street", "Dubai", "United Arab Emirates",
                    "0097150xxxxxxx", "009714xxxxxxx", "shameer@mycompany.com"));
            customer.setAddressSecondary(prepareAddress("234", "My Home Building",
                    "My Home Street", "Dubai", "United Arab Emirates",
                    "0097150xxxxxxx", "009714xxxxxxx", "shameer@myhome.com"));
            return customer;
        }

        private Name prepareCustomerName() {
           
            Name name = new Name();
            name.setLName("Shameer");
            name.setFName("Kunjumohamed");
            return name;
        }

        private Address prepareAddress(String doorNo, String building,
                String street, String city, String country, String phoneMobile,
                String phoneLandline, String email) {
           
            Address address = new Address();
            address.setDoorNo(doorNo);
            address.setBuilding(building);
            address.setStreet(street);
            address.setCity(city);
            address.setCountry(country);
            address.setPhoneMobile(phoneMobile);
            address.setPhoneLandLine(phoneLandline);
            address.setEmail(email);
            return address;
        }
    }
    OrderServiceClientTest.java



  6.  Finally you will have a package structure like this..




  7. Run the OrderServiceClientTest.java - Right click on the file, Run as --> JUnit Test. (Make sure your web service is running). You should be able to see the success message as given below, if your configurations are correct.



  8. Check your log files or the Console view of your Eclipse (or STS), to see the SOAP message logged, and other debug messages.

Now you have your client program ready with you, hope it was straight forward and easily understandable. You may send in the error messages you may get while developing the program as comments here, along with the stack trace, I will try to find some time to check them out and reply. 


The best part you can see here is that you are not writing any web-service specific code in your program, rather you are just invoking the appropriate method of the WebServiceTemplate. Rest all job is done by the framework (provided you configured the components properly).

The above sample program is just one way of developing a web service client program using Spring Web Services. There are many other approaches you can try with, for example, using a different marshaller, say, XMLBeans. You can even construct your own XML, send via a StreamSource and recieve the response as XML as a StreamResult, then manipulate the XML using XPATH. 

 Besides the default HTTPTransport, you may just try the JMSTransport and the EmailTransport as well. It's all fun :) 


Lets us move on to add some security to our web service and client programs in the next few posts. Expect them soon..