Sunday, March 04, 2012

Spring-Web Service 2 Using JAXB and Maven Complete Tutorial


Note : This tutorial only covers simple setup and working code for Spring Web Service using Maven and JAXB.

Prerequisites
JDK 1.6
Eclipse Hellos
M2e plugin for Maven (get it from Eclipse marketplace)
Tomcat 7.0

JAXB plugin for Eclipse (get it from http://java.net/downloads/jaxb-workshop/IDE%20plugins/org.jvnet.jaxbw.zip)
or
JAXB Maven plugin (ref  :  http://www.altuure.com/2008/01/22/jaxb-quickstart-via-maven2/  and http://mojo.codehaus.org/jaxb2-maven-plugin/index.html)

Recommendation : first go through Spring-WS documentation 

Aim of the experiment 
Write a service for HR which will update leave request.
Input
Employee : employee number, first name, last name
Leave : start date, end date

Output
Status : status code, description

Step - 1
Create a maven project in Eclipse.
Here is my 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.mycompany.hr</groupId>
<artifactId>holidayWSService</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>holidayService Spring-WS Application</name>
<url>http://www.springframework.org/spring-ws</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<finalName>holidayService</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<packagingExcludes>WEB-INF/web.xml</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.mycompany.hr.jaxb.model</packageName> <!-- The name of your generated source package -->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<schemaDirectory>${basedir}/src/main/resources/xsd</schemaDirectory>
<clearOutputDir>false</clearOutputDir>
<schemaFiles>hr.xsd</schemaFiles>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-test</artifactId>
<version>2.0.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.0.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

My Project structure :

Step 2
Write an XSD

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:hr="http://mycompany.com/hr/schemas"
        elementFormDefault="qualified"
        targetNamespace="http://mycompany.com/hr/schemas">
    <xs:element name="LeaveRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="Leave" type="hr:LeaveType"/>
                <xs:element name="Employee" type="hr:EmployeeType"/>              
            </xs:all>
        </xs:complexType>
    </xs:element>
    <xs:element name="LeaveResponse">
        <xs:complexType>
            <xs:all>
                <xs:element name="Status" type="hr:StatusType"/>            
            </xs:all>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="LeaveType">
        <xs:sequence>
            <xs:element name="StartDate" type="xs:date"/>
            <xs:element name="EndDate" type="xs:date"/>              
        </xs:sequence>                                                    
    </xs:complexType>
    <xs:complexType name="EmployeeType">
        <xs:sequence>
            <xs:element name="Number" type="xs:integer"/>
            <xs:element name="FirstName" type="xs:string"/>
            <xs:element name="LastName" type="xs:string"/>    
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="StatusType">
        <xs:sequence>
            <xs:element name="StatusCode" type="xs:integer"/>
            <xs:element name="StatusDesc" type="xs:string"/>              
        </xs:sequence>                                                    
    </xs:complexType>
</xs:schema>

Step 3
Generate domain classes using JAXB

using JAXB Eclipse plugin


or using Maven plugin
(right click on project ) -> Run As --> Run configuration  [Goal s: jaxb2:xjc]

You can run "mvn jaxb2:xjc" through command line as well.
Warning : Please be careful while generating the JAXB files, it may delete your existing required files in the base folder. If it happens, then you may recover those files from Eclipse history, this may not recover fully.

Strp 4
update your configuration files

Here is my 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">
 
    <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>
</web-app>


Note : 1. "MessageDispatcherServlet" is configured
           2. "transformWsdlLocations" marked as true. Please ref to Spring-WS documentaion.

spring-ws-servlet.xml file

<?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/oxm
        http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd
        http://www.springframework.org/schema/context                      
http://www.springframework.org/schema/context/spring-context-3.1.xsd"
xmlns:oxm="http://www.springframework.org/schema/oxm">

<context:annotation-config />
<context:component-scan base-package="com.mycompany.hr"/>
<sws:annotation-driven/>

<oxm:jaxb2-marshaller id="marshaller"
contextPath="com.mycompany.hr.jaxb.model" />
<!-- <sws:static-wsdl id="hr" location="wsdl/hr.wsdl"/> -->

<!-- If dynamic wsdl need to expose -->
<sws:dynamic-wsdl id="holiday" serviceName="leave"
portTypeName="HumanResource" locationUri="http://localhost:8082/holidayService/"
targetNamespace="http://mycompany.com/hr/definitions">
<sws:xsd location="classpath:/xsd/hr.xsd" />
</sws:dynamic-wsdl>

<bean id="exceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="defaultFault" value="SERVER" />
<property name="exceptionMappings">
<value>
org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
</value>
</property>
</bean>
</beans>
Note : 1. "<context:annotation-config/>" will look for all the annotated classes.
           2. "oxm:jaxb2-marshaller" looks for JAXB generated classes.
           3.  "sws:dynamic-wsdl" will create the wsdl file dynamically, but it's not recommended , once you get the wsdl from browser , change to static ""sws:static-wsdl" (ref: Spring-WS documentation).
          4. id="holiday" in "sws:dynamic-wsdl" , the wsdl will be exposed under "holiday.wsdl" (ref : Step 7)

Step 5
Writing the Endpoint and Service classes
Here is my Endpoint implementation class (HolidayEndPointImpl.java)

package com.mycompany.hr.ws;

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.mycompany.hr.jaxb.model.LeaveRequest;
import com.mycompany.hr.jaxb.model.LeaveResponse;
import com.mycompany.hr.service.HolidayService;

@Endpoint
public class HolidayEndpointImpl implements HolidayEndpoint{

private HolidayService holidayService;
@Autowired
public void setHolidayService(HolidayService holidayService) {
this.holidayService = holidayService;
}

@PayloadRoot(localPart = "LeaveRequest",namespace = "http://mycompany.com/hr/schemas")
@ResponsePayload
public LeaveResponse applyLeave(@RequestPayload LeaveRequest LeaveRequest){
return holidayService.applyHoliday(LeaveRequest);
}
}

Note: LeaveRequest and LeaveResponse classes are JAXB generated classes.

Here is my service implementation class (HolidayServiceImpl.java)

package com.mycompany.hr.service;

import java.math.BigInteger;

import org.springframework.stereotype.Service;

import com.mycompany.hr.jaxb.model.LeaveRequest;
import com.mycompany.hr.jaxb.model.LeaveResponse;
import com.mycompany.hr.jaxb.model.StatusType;

@Service
public class HolidayServiceImpl implements HolidayService{

@Override
public LeaveResponse applyHoliday(LeaveRequest leaveRequest) {
LeaveResponse leaveResponse = new LeaveResponse();
StatusType statusType = new StatusType();
if(leaveRequest.getLeave() == null){
statusType.setStatusCode(new BigInteger("1000"));
statusType.setStatusDesc("Leave Request Object missing");
leaveResponse.setStatus(statusType);
return leaveResponse;
}

if(leaveRequest.getLeave().getStartDate().compare(leaveRequest.getLeave().getEndDate()) > 0){
statusType.setStatusCode(new BigInteger("2000"));
statusType.setStatusDesc("Start Date should be before End Date");
leaveResponse.setStatus(statusType);
}else{
statusType.setStatusCode(new BigInteger("111"));
statusType.setStatusDesc("SUCCESS");
leaveResponse.setStatus(statusType);
}
return leaveResponse;
}

}

Step 6
Build and deploy in Tomcat

Step 7
Go to http://localhost:8082/holidayService/holiday.wsdl  (I have configured my tomcat for 8082 port)
Now you can see the wsdl file, you can use it for static loading. (ref: Step 4).

Step 8
Test the web-service.

For testing the web-service using  Eclipse plugin please ref : http://www.eclipse.org/webtools/jst/components/ws/1.0/tutorials/WebServiceExplorer/WebServiceExplorer.html