Sunday, August 2, 2009

Spring Web Service

Spring-WS supports the contract-first development style that means we start with the WSDL contract and use Java to implement said contract.

We will write a sample code to demonstrate how to write contract-first Web services, that is, developing web services that start with the XML Schema/WSDL contract first followed by the Java code second.

Let us take an example where any service provider maintain user database and provides services for updating user information, providing user information (user profile) and authenticating user. Let say user profile has login id, first name, last name, email and address. We want to provide services like AuthenticateUser, UpdateUserProfile, GetUserProfile.

Let us follow the following steps -

1. So, first step would be to create XSD (let us name it as user.xsd). Go through the below schema, it is self-explanatory

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.mysite.com/user"
xmlns:tns="http://www.mysite.com/user"
elementFormDefault="qualified">

<element name="AuthenticateUser">
<complexType>
<sequence>
<element name="LoginId" type="string" />
<element name="Password" type="string" />
</sequence>
</complexType>
</element>


<element name="UpdateUserProfile">
<complexType>
<all>
<element name="Profile" type="tns:UserProfile" />
<element name="LoginId" type="string" />
</all>
</complexType>
</element>

<element name="GetUserProfile">
<complexType>
<sequence>
<element name="LoginId" type="string" />
</sequence>
</complexType>
</element>

<complexType name="UserProfile">
<sequence>
<element name="loginId" type="string" />
<element name="firstName" type="string" />
<element name="lastName" type="string" />
<element name="emailId" type="string" />
<element name="address" type="string" />
</sequence>
</complexType>

<element name="GetUserProfileResponse" type="tns:UserProfile"/>

<element name="AuthenticateUserResponse" type="int" />

<element name="UpdateProfileResponse" type="int" />

</schema>

2. Now that the schema is created, we will generate supporting classes. We will use JAXB to generate the classes. Download JAXB and unzip it. Use the following command to generate the classes -

%JAXB_HOME%\bin\xjc.bat -d . -p test.schema ./config/user.xsd

The target package is test.schema and schema is under config directory. This would generate the classes based on above user schema.

OR we can use the following build.xml using "ant generate"

<?xml version="1.0"?>
<project name="spring-ws-user-ws-server" basedir="." default="default">
<path id="classpath">
<fileset dir="C:\jaxb-ri-20090206\lib">
<include name="**/*.jar"/>
</fileset>
</path>


<target name="generate">

<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask" classpathref="classpath" />

<xjc destdir="./" package="test.schema" >
<schema dir="./config" includes="user.xsd"/>
</xjc>

</target>

</project>


3. Implementing the Endpoint - In Spring-WS, we implement Endpoints to handle the incoming XML messages. There are two flavors of endpoints: message endpoints and payload endpoints. Message endpoints give access to the entire XML message, including SOAP headers. Typically, the endpoint will only be interested in the payload of the message that is the contents of the SOAP body. In that case, creating a payload endpoint makes more sense.

To handle XML we can use various marshalling techniques like JAXB, Castor,XStream etc apart from JDOM, DOM, dom4j, XOM, SAX,and StAX. We will use JAXB marshalling technique.

package test.ws;

import javax.xml.bind.JAXBElement;

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

import test.schema.AuthenticateUser;
import test.schema.GetUserProfile;
import test.schema.ObjectFactory;
import test.schema.UpdateUserProfile;
import test.schema.UserProfile;
import test.service.IUserService;

@Endpoint
public class UserEndPoint {

IUserService userService;

private ObjectFactory objectFactory = new ObjectFactory();

public UserEndPoint(IUserService userService) {
this.userService = userService;
}

@PayloadRoot(localPart = "AuthenticateUser", namespace = "http://www.mysite.com/user")
public JAXBElement<Integer> authenticateUser(AuthenticateUser request) {
System.out.println(request.getLoginId() + " " + request.getPassword());
int result = userService.authenticateUser(request.getLoginId(), request
.getPassword());
return objectFactory.createAuthenticateUserResponse(result);
}

@PayloadRoot(localPart = "UpdateUserProfile", namespace = "http://www.mysite.com/user")
public JAXBElement<Integer> updateUserProfile(UpdateUserProfile request) {
int result = userService.updateUserProfile(request.getLoginId(),
request.getProfile());
return objectFactory.createUpdateProfileResponse(result);
}

@PayloadRoot(localPart = "GetUserProfile", namespace = "http://www.mysite.com/user")
public JAXBElement<UserProfile> getUserProfile(GetUserProfile request) {
UserProfile userProfile = userService.getUserProfile(request
.getLoginId());
return objectFactory.createGetUserProfileResponse(userProfile);
}
}


4. We have injected userService to above end point, the service interface looks like -

package test.service;

import test.schema.UserProfile;

public interface IUserService {
public int authenticateUser(String loginId, String password);

public int updateUserProfile(String loginId, UserProfile profile);

public UserProfile getUserProfile(String loginId);
}

5. Routing the Message to the Endpoint - I have used annotation to configure Endpoint and PayloadRoot. See @Endpoint and @PayloadRoot. let us define applicationContext-ws.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:oxm="http://www.springframework.org/schema/oxm"
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-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-1.5.xsd">


<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/user.xsd"/>
</bean>

<bean id="userEndPoint" class="test.ws.UserEndPoint">
<constructor-arg ref="userService"/>
</bean>

<oxm:jaxb2-marshaller id="marshaller" contextPath="test.schema"/>

<bean id="annotationMapping"
class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
</bean>

<sws:marshalling-endpoints/>
</beans>

Also we define applicationContext.xml where we define the userService bean

<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:annotation-config/>

<bean id="userService" class="test.service.UserService" />
</beans>


6. Publishing the WSDL - we don't have to write a WSDL; Spring-WS can generate itself. This is how ws-servlet.xml looks like

<?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:util="http://www.springframework.org/schema/util" 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="user" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema"/>
<property name="portTypeName" value="User"/>
<property name="locationUri" value="/userService"/>
</bean>

</beans>

- The bean id determines the URL where the WSDL can be retrieved. Here the bean id is user that means the WSDL can be retrieved as user.wsdl in the servlet context. The full URL will be like http://localhost:8080/UserService/user.wsdl.

- The schema property refers to the user schema defined in point 1. We will place the schema in the WEB-INF directory of the application.

- We will define the WSDL port type to be User

- We set the location where the service can be reached: /UserService/

7. Now let us write web.xml, which looks like

<?xml version="1.0" encoding="ISO-8859-1"?>

<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>Spring WS</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-ws.xml
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>

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

<mime-mapping>
<extension>xsd</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

8. Now let us deploy the web application.
- Copy user.xsd, web.xml, applicationContext-ws.xml, applicationContext.xml, ws-servlet.xml under /WEB-INF/
- Copy all required libs under WEB-INF/lib - spring-ws-1.5.6-all.jar, spring-ws-core-1.5.6.jar, spring-webmvc.jar, spring-oxm-1.5.6.jar, spring.jar, commons-logging-1.0.4.jar, wsdl4j-1.6.1.jar, XmlSchema-1.4.3.jar
- Copy the UserEndPoint class, all supporting classes and service classes under WEB-INF/class and restart tomcat


Create WAR and deploy under Tomcat. We have successfully deployed the webservice. Check the WSDL using http://localhost:8080/UserService/user.wsdl

We will next write the dummy client. But after a short break :)

1 comment:

Rick said...

Thanks much for the clear and simple explanation.