Friday, June 13, 2014

A Simple Web Service Using Spring-WS

In this post I will describe the way to write a very simple SOAP-based web service that will take as input two numbers and return the sum of those numbers.

Create the Project Structure:
We can start our development based on a Java-Web project using maven. [ See maven web project directory layout  and creating maven webapp project ].

Project Structure



Define Data Contract (XSD):
Spring-WS focuses on Contract-First development style [See Why]. So, we first define the data exchange (reuest-response) format of the web service, i.e. the Data Contract.

webapp/WEB-INF/spring/service-definition.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://develop-for-fun.blogspot.com/spring-ws" xmlns:wst="http://develop-for-fun.blogspot.com/spring-ws">
  
  <xs:element name="AddServiceRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="firstNumber" type="xs:integer"/>
        <xs:element name="secondNumber" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="AddServiceResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="sum" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
</xs:schema>


Generate Request/Response Models:
You can generate (un-marshal) Java classes using JAXB from the above XSD. You can use XJC for this or you can use features of your IDE to convert XML-to Java. In IntelliJ IDEA, right click on the .xsd file in the project explorer, click "Web Services" --> "Generate Java classes from XML Schema using JAXB". (for eclipse see this).
AddServiceRequest.java, AddServiceResponse.java and ObjectFactory.java will be generated in this case. Put the generated classes in a separate package (e.g. com.test.ws.wsmodels).

Then write the service interfaces and an implementation of the service interface.

AddService.java

package com.test.ws.services;

import java.math.BigInteger;

public interface AddService {
 public BigInteger addNumbers(BigInteger number1, BigInteger number2);
}

AddServiceImpl.java

package com.test.ws.services;

import java.math.BigInteger;

import org.springframework.stereotype.Service;

@Service
public class AddServiceImpl implements AddService {

 /**
  * Returns the sum of two input numbers.
  */
 public BigInteger addNumbers(BigInteger number1, BigInteger number2) {
  return number1.add(number2);
 }

}
Don't forget the @service annotation.

Now write the endpoint class.

AdditionEndpoint.java

package com.test.ws.endpoints;

import org.springframework.beans.factory.annotation.Autowired;
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 com.test.ws.wsmodels.*;
import com.test.ws.services.AddService;

@Endpoint
public class AdditionEndpoint {
   //To calculate sum of two input.
   @Autowired
   private AddService addService;
   
   //This is similar to @RequestMapping of Spring MVC
   @PayloadRoot(localPart="AddServiceRequest", namespace="http://develop-for-fun.blogspot.com/spring-ws")
   @ResponsePayload
   public AddServiceResponse generateRandomNumber(@RequestPayload AddServiceRequest request) {
       
    AddServiceResponse response = new ObjectFactory().createAddServiceResponse();
       
    response.setSum(addService.addNumbers(request.getFirstNumber(), request.getSecondNumber()) );
       
    return response;
    
   }
}


All the java classes are ready now. We need to configure the spring servlet definition and the web.xml file.

webapp/WEB-INF/spring/ws-servlet.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" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:sws="http://www.springframework.org/schema/web-services"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd">

 <sws:annotation-driven />

 <!-- To detect @Service, @Endpoint etc -->
 <context:component-scan base-package="com.test.ws" />

 <!-- To generate dynamic wsdl -->
 <sws:dynamic-wsdl id="wstest" portTypeName="add"
  locationUri="/services/add" targetNamespace="http://develop-for-fun.blogspot.com/spring-ws">
  <sws:xsd location="/WEB-INF/spring/service-definitions.xsd" />
 </sws:dynamic-wsdl>

 <!-- For validating your request and response -->
 <!-- So that you don't send a string instead of an integer -->

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


</beans>

webapp/WEB-INF/web.xml

<?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 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>Addtion Service</display-name>

    <servlet>
        <servlet-name>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>
  <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/spring/ws-servlet.xml
            </param-value>
        </init-param>
    </servlet>

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

</web-app>



Now the maven configuration.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.test.ws</groupId>
 <artifactId>spring-ws-test</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>Spring-WS Application</name>
 <url>http://www.springframework.org/spring-ws</url>
 <build>
  <finalName>spring-ws-test</finalName>
  <plugins>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.5</source>
     <target>1.5</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>tomcat-maven-plugin</artifactId>
    <version>1.1</version>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <executions>
     <execution>
      <goals>
       <goal>xjc</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <!-- This should the location of the schema files (*.xsd) for services definition -->
     <schemaDirectory>src/main/webapp/WEB-INF/spring</schemaDirectory>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <dependencies>
  <dependency>
   <groupId>org.springframework.ws</groupId>
   <artifactId>spring-ws-core</artifactId>
   <version>2.0.1.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.0</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.0.3</version>
  </dependency>
  <dependency>
   <groupId>org.apache.xmlbeans</groupId>
   <artifactId>xmlbeans</artifactId>
   <version>2.4.0</version>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.1</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <scope>compile</scope>
   <version>1.2.16</version>
  </dependency>
 </dependencies>
</project>


Now build the file using the maven command:
mvn clean package

Deploy the spring-ws-test.war file in your server (tomcat / glassfish / jboss). If you deploy the application in your localhost, then you can see the wsdl on your browser in the following URL:
http://localhost:8080/spring-ws-test/services/wstest.wsdl

The service can be tested using Soap-UI or other SOAP client.

You can get the whole project from github using git:
git clone https://github.com/tariqmnasim/spring-ws-test.git


References:
JAXB: