Posts Tagged ‘SOAP’

Notes on the Spring-WS tutorial

September 26, 2009

Today I have reviewed a proof of concept I have done in order to decide which Web Services framework matches the requirements of a bioinformatics project. Because of forgetting to store a file in the versioning system, I had to review the whole tutorial and to look for the file within the Spring tutorial. What is interesting, until now nobody published the full content of the Spring-WS tutorial.

So, here is my pom.xml. I am using Apache Maven2 in order to strictly enforce the versions of the JAR files. Nobody is constrained to use Maven, but without it, it would be very difficult to sort out the transient dependencies.

<?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.razvan.hr</groupId>
 <artifactId>holidayService</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>holidayService Spring-WS Application</name>
 <url>http://www.springframework.org/spring-ws</url>
 <build>
 <finalName>holidayService</finalName>
 <plugins>
 <plugin>
 <groupId>org.mortbay.jetty</groupId>
 <version>6.1.9</version>
 <artifactId>maven-jetty-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
 <dependencies>
 <dependency>
 <groupId>org.springframework.ws</groupId>
 <artifactId>spring-ws-core</artifactId>
 <version>1.5.8</version>
 </dependency>
 <dependency>
 <groupId>jdom</groupId>
 <artifactId>jdom</artifactId>
 <version>1.0</version>
 </dependency>
 <dependency> <!-- required by AbstractJDomPayloadEndpoint -->
 <groupId>jaxen</groupId>
 <artifactId>jaxen</artifactId>
 <version>1.1</version>
 <scope>runtime</scope>
 </dependency>
 <dependency>
 <groupId>javax.xml.soap</groupId>
 <artifactId>saaj-api</artifactId>
 <version>1.3</version>
 <scope>runtime</scope>
 </dependency>
 <dependency>
 <groupId>com.sun.xml.messaging.saaj</groupId>
 <artifactId>saaj-impl</artifactId>
 <version>1.3</version>
 <scope>runtime</scope>
 </dependency>
 <dependency>
 <groupId>xalan</groupId>
 <artifactId>xalan</artifactId>
 <version>2.7.1</version>
 </dependency>
 </dependencies>
</project>

I am using the jetty plugin in order to be able to run the application right from the command line; xalan dependency is required in order to avoid the exception “java.lang.IllegalStateException: Could not find SAAJ on the classpath” thrown by Spring-WS, corrected in a newer version of xalan.

Also according to the tutorial, the file spring-ws-servlet.xml, a regular Spring context file, describes the external interface of the Web Service and the associated Java classes. This file has to be placed in the src/main/webapp/WEB-INF directory.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 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">

 <bean id="holidayEndpoint" class="com.razvan.hr.ws.HolidayEndpoint">
 </bean>

 <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
 <property name="mappings">
 <props>
 <prop key="{http://mycompany.com/hr/schemas}HolidayRequest">holidayEndpoint</prop>
 </props>
 </property>
 <property name="interceptors">
 <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" />
 </property>
 </bean>

 <bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
 <property name="builder">
 <bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
 <property name="schema" value="/WEB-INF/hr.xsd" />
 <property name="portTypeName" value="HumanResource" />
 <property name="locationUri" value="http://localhost:8080/holidayService/" />
 </bean>
 </property>
 </bean>

</beans>

And here is the implementation of com.razvan.hr.ws.HolidayEndpoint. According to Maven customaries, this have to be placed in the src/main/java subdirectory of the project. I have tried to keep the implementation minimal by avoiding further service instantiations, unrelated to Spring-WS:

package com.razvan.hr.ws;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.xpath.XPath;
import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint;

public class HolidayEndpoint extends AbstractJDomPayloadEndpoint {

 private XPath startDateExpression;

 private XPath endDateExpression;

 private XPath nameExpression;

 public HolidayEndpoint() throws JDOMException {
 Namespace namespace = Namespace.getNamespace("hr", "http://mycompany.com/hr/schemas");
 startDateExpression = XPath.newInstance("//hr:StartDate");
 startDateExpression.addNamespace(namespace);
 endDateExpression = XPath.newInstance("//hr:EndDate");
 endDateExpression.addNamespace(namespace);
 nameExpression = XPath.newInstance("concat(//hr:FirstName,' ',//hr:LastName)");
 nameExpression.addNamespace(namespace);
 }

 protected Element invokeInternal(Element holidayRequest) throws Exception {
 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
 Date startDate = dateFormat.parse(startDateExpression.valueOf(holidayRequest));
 Date endDate = dateFormat.parse(endDateExpression.valueOf(holidayRequest));
 String name = nameExpression.valueOf(holidayRequest);

 // do something meaningful with the extracted data.
 System.out.println("Start date: "+startDate+" end Date: "+ endDate+" Employee: "+name);
 return null;
 }
}

My final file structure is:

|-- pom.xml
`-- src
 `-- main
   |-- java
   |   `-- com
   |       `-- razvan
   |           `-- hr
   |               `-- ws
   |                   `-- HolidayEndpoint.java
   `-- webapp
     `-- WEB-INF
        |-- hr.xsd
        |-- spring-ws-servlet.xml
        `-- web.xml

The web.xml is pretty standard for Spring applications:

<?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>Spring test application</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>

This is the whole Web Service application. In order to run the application try from the command line:

mvn clean install jetty:run

This will compile the war file and start an instance of the jetty web server.

By accessing the address: http://localhost:8080/holidayService/holiday.wsdl you can see the generated WSDL associated to your described web service. While leaving the jetty running, you may attempt to create a client, in order to see the message printed within the invokeInternal method. Following it will be described a Java client, but it is possible to build your client using any other language or platform.

Using the Eclipse wizard create a new empty Java project, than create using the wizard a new “Web Service Client”. Provide the address of the wsdl and you will have a bunch of Java classes describing your web service and the required XML parsers dependencies in your newly created project. Create a new main class in the default package named TestWs.java, with the following content:

import java.math.BigInteger;
import java.util.Date;

import com.mycompany.hr.schemas.EmployeeType;
import com.mycompany.hr.schemas.HolidayRequest;
import com.mycompany.hr.schemas.HolidayType;
import com.mycompany.hr.schemas.HumanResource;
import com.mycompany.hr.schemas.HumanResourceServiceLocator;

public class TestWs {
 public static void main(String[] args) {
 try {
 HumanResourceServiceLocator loc = new HumanResourceServiceLocator();
 HumanResource myserv = loc.getHumanResourcePort();

 HolidayType ht = new HolidayType(new Date(), new Date());
 EmployeeType et = new EmployeeType(new BigInteger("100"), "Razvan",
 "Popovici");
 HolidayRequest req = new HolidayRequest(ht, et);

 myserv.holiday(req);
 } catch (Exception e) {
 e.printStackTrace();
 }
 System.out.println("OK!");

 }
}

Run the class, and you will be able to observe the message in the maven window.

A few conclusions of the test are:

– Spring-WS is a great approach if the schema definitions of the data model already exists.

– The required xalan dependency is a bug in Spring-WS dependency requirements, fortunately Maven made possible the correction.

– The element wsdl:definitions is optional, the tutorial worked without it as well.