Posts Tagged ‘web services’

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.

Advertisements

EJB3 Remote client: Hello world vs production usage

April 17, 2008

Yesterday I have made the first remote client for an Session bean. Hello world bean worked great, with a little research about how to configure the JNDI client to connect to the Glassfish server and what to provide in the CLASSPATH. I had to duplicate the remote interface and to delete the EJB annotation, since these JARs shouldn’t be imported on the client. I won’t do it in a production application, so I am ready for J2EE dependencies on the client as well.

The evil is as usual in detail. I don’t use EJB to write Hello word software. My session beans work with complex objects, persisted as entities. The beans do refer another beans, in one-to-many relations. In order to load such a list, the client needs to depend on the persistence library of the server, since that list is not a “vanilla” java.util.HashSet class, but one from TopLink or Hibernate, depending on the chosen EJB persistence.

So, near the entity beans, I had to import also the TopLink libraries in the client.

A solution to move from this dependency forest would be to employ web services. Regarding them, my experiences were not encouraging: they crash when receiving a reflexive reference (object A references object B and B references A) and they suffer performance issues both on server and client side when serving large collections.

Another ideas, is to use the value object pattern. This would mean at least duplication of my data model; but remember, I use MDA, so my model is generated from an UML model. Maybe the build of some plain objects could provide a special client JAR file (beans and service interfaces) and a copy mechanism from the beans, to be used on server side.