Automatic translation

Archives

February 2010
L My Me J V S D
"January March "
A 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

Contributors

Web service code first with jax-ws

Projects evolve during development, the architecture also ...

Background

When our project started, we used a standard architecture: Liferay portal, portlet application containing the jar it needs (including jar of access to the business layer), all based on the couple magic Spring / Hibernate .

But then, as and when the project progresses, we found that:

  • This architecture included some flaws (such as cache management, the size of the portlets and the impact of a change in a jar on the business re-deploy all portlets).
  • New requirements have emerged as the opening of our system to an external application (Intalio in this case), the only means of communication are the web services.

The conclusion was clear: we must move to a web services architecture with portlets jar containing only the presentation, the business components are grouped together in a web app, communication between these two layers is achieved via of web services.

The choice of the "code first"

Many articles on the web with the approach "contract first" as good practice to use for the implementation of web services. Spring also provides a very interesting tool in this area Spring Web Services - Reference Documentation .

Theoretically this approach is most relevant but here, we are not at the beginning of the project:

  • delays are, as in most projects, tight.
  • all business services already exist. In the context of the implementation "contract first" it would be necessary to define the appropriate xsd, the marshalling / unmarshalling, ...

Our research referrals are based on the criteria of time and cumbersome to implement, after a certain time, you have to be pragmatic. We then ran into JAX-WS, which met almost all criteria (except the best practices).

This article will present the implementation of web services with spring jax-ws. There are already some posts on the web on this subject but none is usable as is (especially in terms of dependencies).

The pom.xml

The examples of using JAX-WS with Spring are many on the web but none of the dependencies specifies exactly to include in the project. The most difficult has been to design the following 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 "> 4.0 <modelVersion> .0 </ modelVersion> <packaging> war </ packaging> <artifactId> sample-jax-ws-spring </ artifactId> <groupId> fr.ippon.sandbox </ groupId> <version> 1.0-SNAPSHOT </ version> Ippon <name> Sandbox - Sample JAX WAS spring </ name> <! - ================= -> <! - Build plugins = = -> < ! - ================= -> <build> <! - Definition compiler options -> <plugins> <plugin> <artifactId> maven-compiler -plugin </ artifactId> <configuration> <source> 1.6 </ source> <target> 1.6 </ target> <encoding> ISO-8859-1 </ encoding> <debug> true </ debug> </ configuration> < / plugin> <plugin> <groupId> org.apache.maven.plugins </ groupId> <artifactId> maven-resources-plugin </ artifactId> <configuration> <encoding> ISO-8859-1 </ encoding> </ configuration > </ plugin> </ plugins> </ build> <! - ================= -> <! - Dependencies = = -> <! - - ================= -> <dependencies> <! - Spring dependencies -> <dependency> <groupId> org.springframework </ groupId> <artifactId> spring -web </ artifactId> <version> 2.5.5 </ version> </ dependency> <! - Spring remoting dependencies -> <dependency> <groupId> org.springframework </ groupId> <artifactId> spring-remoting < / artifactId> <version> 2.0.8 </ version> </ dependency> <dependency> <groupId> javax.xml </ groupId> <artifactId> jaxrpc-api </ artifactId> <version> 1.1 </ version> </ dependency> <! - Spring Web services dependencies -> <dependency> <groupId> org.springframework.ws </ groupId> <artifactId> spring-ws-core </ artifactId> <version> 1.5.8 </ version> </ dependency> <dependency> <groupId> org.springframework.ws </ groupId> <artifactId> spring-xml </ artifactId> <version> 1.5.8 </ version> </ dependency> <! - JAX-WS dependencies -> <dependency> <groupId> org.jvnet.jax-ws-commons.spring </ groupId> <artifactId> jaxws-spring </ artifactId> <version> 1.8 </ version> <exclusions> <exclusion> < groupId> org.springframework </ groupId> <artifactId> spring </ artifactId> </ exclusion> <exclusion> <groupId> com.sun.xml.stream.buffer </ groupId> <artifactId> streambuffer </ artifactId> </ exclusion> <exclusion> <groupId> org.jvnet.staxex </ groupId> <artifactId> stax-ex </ artifactId> </ exclusion> </ exclusions> </ dependency> <dependency> <artifactId> streambuffer </ artifactId> <groupId> com.sun.xml.stream.buffer </ groupId> <version> 1.0 </ version> </ dependency> <dependency> <groupId> com.sun.xml.ws </ groupId> jaxws-<artifactId> rt </ artifactId> <version> 2.2 </ version> <exclusions> <exclusion> <groupId> com.sun.istack </ groupId> <artifactId> IStackable-commons-runtime </ artifactId> </ exclusion> <exclusion> <groupId> woodstox </ groupId> <artifactId> wstx-asl </ artifactId> </ exclusion> </ exclusions> </ dependency> <dependency> <groupId> com.sun.istack </ groupId> <artifactId> IStackable- commons-runtime </ artifactId> <version> 2.2 </ version> </ dependency> <! - Web -> <dependency> <groupId> javax.servlet </ groupId> <artifactId> jsp-api </ artifactId> <version> 2.0 </ version> <scope> Provided </ scope> </ dependency> <dependency> <groupId> javax.servlet </ groupId> <artifactId> servlet-api </ artifactId> <version> 2.4 </ version > <scope> Provided </ scope> </ dependency> <dependency> <groupId> javax.servlet </ groupId> <artifactId> jstl </ artifactId> <version> 1.1.2 </ version> <scope> runtime </ scope> </ dependency> <! - Commons dependencies -> <dependency> <groupId> commons-logging </ groupId> <artifactId> commons-logging </ artifactId> <version> 1.1.1 </ version> <exclusions > <exclusion> <groupId> log4j </ groupId> <artifactId> log4j </ artifactId> </ exclusion> <exclusion> <groupId> logk </ groupId> <artifactId> logk </ artifactId> </ exclusion> <exclusion> <groupId> avalon-framework </ groupId> <artifactId> avalon-framework </ artifactId> </ exclusion> <exclusion> <groupId> javax.servlet </ groupId> <artifactId> servlet-api </ artifactId> </ exclusion > </ exclusions> </ dependency> <dependency> <groupId> log4j </ groupId> <artifactId> log4j </ artifactId> <version> 1.2.13 </ version> </ dependency> <! - Testing -> <dependency> <groupId> junit </ groupId> <artifactId> junit </ artifactId> <version> 4.4 </ version> </ dependency> <dependency> <groupId> org.springframework </ groupId> <artifactId> spring-test </ artifactId> <version> 2.5.5 </ version> <scope> test </ scope> </ dependency> </ dependencies> <! - ================ -> <! - repositories = = -> <! - ================ -> <repositories> <repository> <id> jboss </ id> <url> http://repository.jboss.org/maven2 </ url> </ repository> <repository> <id>-Java.net maven2 </ id> <url> http://download.java.net/ maven / 2 </ url> </ repository> </ repositories> </ project> 

The business layer

To be as close as possible to a real application, we use the business layer as follows:

  • The POJO
  fr.ippon.sandbox.sample.model package; 

 import java.util.Date; 

 public class Sample {
   private String code;
   private String label;
   private Date date; 

   public void setCode (String code) {
     this.code = code;
   }
   getCode public String () {
     return code;
   } 

   public void setLabel (String label) {
     this.label = label;
   }
   public String getLabel () {
     return label;
   } 

   public void setDate (Date date) {
     this.date = date;}
   public Date getDate () {
     return date;
   }
 } 
  • The interface of the business service
  fr.ippon.sandbox.sample.service package; 

 fr.ippon.sandbox.sample.model.Sample import; 

 SampleService {public interface
   Sample [] findSamples (String ACOD);
 } 
  • The implementation of business service
  fr.ippon.sandbox.sample.service package;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 fr.ippon.sandbox.sample.model.Sample import; 

 public class implements SampleServiceImpl SampleService { 

   public Sample [] findSamples (String ACOD) {
     List <SAMPLE> <SAMPLE> samples = new ArrayList ();
     samples.add (createSample ("1"));
     samples.add (createSample ("2"));
     samples.add (createSample ("3"));
     return (Sample []) samples.toArray (new Sample [] {});
   }

   Sample private createSample (String aValue) {
     Sample sample = new Sample ();
     sample.setCode ("Code" + aValue);
     sample.setLabel ("Label" + aValue);
     sample.setDate (new Date (System.currentTimeMillis ()));
     sample return;
   }
 } 
  • The file applicationContext-service.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"
 xsi: schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 <bean id="sampleService" class="fr.ippon.sandbox.sample.service.SampleServiceImpl" />

 </ Beans> 

Exposure of the service as web service

Now that we have defined the business layer, we will expose the service Sample [] findSamples (String) as web services.

  • Interface definition of web service
  fr.ippon.sandbox.sample.ws package;

 javax.jws.WebService import;
 javax.jws.soap.SOAPBinding import;
 javax.jws.soap.SOAPBinding.Style import;
 javax.jws.soap.SOAPBinding.Use import;

 fr.ippon.sandbox.sample.model.Sample import;

 @ WebService (targetNamespace = "http://wwww.ippon.fr/sample/", serviceName = "sampleWebService" portname = "sampleWebServicePort")
 @ SOAPBinding (style = Style.RPC, Use.LITERAL use =)
 SampleWebService {public interface

     / **
      * @ See # fr.nantesmetropole.sandbox.sample.service.SampleServiceImpl findSamples (java.lang.String)
      * /
     public Sample [] findSamples (String ACOD);

 } 
  • Setting the implementation of web service
  fr.ippon.sandbox.sample.ws package;

 javax.jws.WebMethod import;
 javax.jws.WebService import;
 javax.jws.soap.SOAPBinding import;
 javax.jws.soap.SOAPBinding.Style import;
 javax.jws.soap.SOAPBinding.Use import;

 fr.ippon.sandbox.sample.model.Sample import;
 fr.ippon.sandbox.sample.service.SampleService import;
 fr.ippon.sandbox.sample.service.SampleServiceImpl import;

 @ WebService (targetNamespace = "http://wwww.ippon.fr/sample/", serviceName = "sampleWebService" portname = "sampleWebServicePort")
 @ SOAPBinding (style = Style.RPC, Use.LITERAL use =)
 public class extends SampleWebServiceImpl SampleServiceImpl implements {SampleWebService

     / **
      * The business service to call
      * /
     private SampleService SampleService;

     / **
      * @ See # fr.nantesmetropole.sandbox.sample.ws.SampleWebService findSamples (java.lang.String)
      * /
     @ Override
     public Sample [] findSamples (String ACOD) {
         getSampleService return (). findSamples (ACOD)
     }

     / **
      * @ Param SampleService SampleService to the set
      * /
     @ WebMethod (exclude = true)
     public void setSampleService (SampleService SampleService) {
         this.sampleService = SampleService;
     }

     / **
      * @ Return the SampleService
      * /
     @ WebMethod (exclude = true)
     public SampleService getSampleService () {
         SampleService return;
     }
 } 

It is important that the statements (See annotations used) are strictly identical between the interface and implementation.

The @ WebService used to specify the declaration of the web service as it will appear in the WSDL file.

The @ WebMethod (exclude = true) to exclude the method of introspection JAX-WS web service that exposes.

  • The file applicationContext-ws.xml
  <? Xml version = '1 .0 '?>

 <Beans xmlns = "http://www.springframework.org/schema/beans" xmlns: xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns: was = "http://jax -ws.dev.java.net/spring/core "
 xmlns: wss = "http://jax-ws.dev.java.net/spring/servlet"
 xsi: schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://jax-ws.dev.java .net / spring / core http://jax-ws.dev.java.net/spring/core.xsd http://jax-ws.dev.java.net/spring/servlet http://jax-ws. dev.java.net / spring / servlet.xsd ">

 <bean id="sampleWebService" class="fr.ippon.sandbox.sample.ws.SampleWebServiceImpl" scope="prototype">
   <property name="sampleService" ref="sampleService" />
 </ Bean>

 <wss:binding url="/services/sampleWebService">
   <wss:service>
     <ws:service bean="#sampleWebService" />
   </ Wss: service>
 </ Wss: binding>

 </ Beans> 

tag wss: binding can expose a bean as web service.

Setting the implementation

  • applicationContext.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"
 xsi: schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 <import resource="applicationContext-service.xml" />
 <import resource="applicationContext-ws.xml" />

 </ Beans> 
  • the web.xml
  <? Xml version = "1.0" encoding = "UTF-8"?>
 <Web-app xmlns: xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://java.sun.com/xml/ns/javaee" xmlns: web = "http : / / java.sun.com/xml/ns/javaee/web-app_2_5.xsd "
 xsi: schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id = "WebApp_ID" version = " 2.5 ">

 <display-name> jax-ws-spring </ display-name>

 <context-param>
   <param-name> contextConfigLocation </ param-name>
   <param-value> classpath *: / context / applicationContext.xml </ param-value>
 </ Context-param>

 <listener>
   <listener-class> org.springframework.web.context.ContextLoaderListener </ listener-class>
 </ Listener>

 <! - These Are for JAX-WS ->
 <servlet>
   <servlet-name> jaxws-servlet </ servlet-name>
   <servlet-class> com.sun.xml.ws.transport.http.servlet.WSSpringServlet </ servlet-class>
 </ Servlet>
 <servlet-mapping>
   <servlet-name> jaxws-servlet </ servlet-name>
   <url-pattern> / services / * </ url-pattern>
 </ Servlet-mapping>

 </ Web-app> 

Verification

  • Integration of a jetty server

To perform our tests, we will use the Jetty server. For this it is necessary to modify the pom.xml of the project.

  <build>
 ...
 <plugins>
   <plugin>
     <groupId> org.mortbay.jetty </ groupId>
     <artifactId> maven-jetty-plugin </ artifactId>
     <version> 6.1.10 </ version>
     <configuration>
       <scanIntervalSeconds> 10 </ scanIntervalSeconds>
       <stopKey> STOP </ stopKey>
       <stopPort> 9999 </ stopPort>
     </ Configuration>
   </ Plugin>
 </ Plugins>
 ...
 </ Build> 

Then just start the server jetty with mvn jetty: run.

  • Check the availability of web service

To view the wsdl corresponding to the web service, simply go to the url http://localhost:8080/sample-jax-ws-spring/services/sampleWebService?wsdl

  <? Xml version = "1.0" encoding = "UTF-8"?>
 <Definitions xmlns: wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns: wsp = "http://www. w3.org/ns/ws-policy "
 xmlns: wsp1_2 = "http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns: WSAM = "http://www.w3.org/2007/05/addressing/metadata" xmlns: soap = "http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns: tns = "http://wwww.ippon.fr/sample/" xmlns: xsd = "http://www.w3.org/2001/XMLSchema" xmlns = "http://schemas.xmlsoap.org/ wsdl / "
 targetNamespace = "http://wwww.ippon.fr/sample/" name = "sampleWebService">
 <types>
   <xsd:schema>
     <Xsd: import namespace = "http://wwww.ippon.fr/sample/"
   </ Xsd: schema>
 </ Types>
 <message name="findSamples">
   <part name="arg0" type="xsd:string"> </ part>
 </ Message>
 <message name="findSamplesResponse">
   <part name="return" type="tns:sampleArray"> </ part>
 </ Message>
 <portType name="SampleWebServiceImpl">
   <operation name="findSamples">
     <input wsam:Action="http://wwww.ippon.fr/sample/SampleWebServiceImpl/findSamplesRequest" message="tns:findSamples"> </ input>
     <output wsam:Action="http://wwww.ippon.fr/sample/SampleWebServiceImpl/findSamplesResponse" message="tns:findSamplesResponse"> </ output>
   </ Operation>
 </ PortType>
 <binding name="sampleWebServicePortBinding" type="tns:SampleWebServiceImpl">
   <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"> </ soap: binding>
   <operation name="findSamples">
     <soap:operation soapAction=""> </ soap: operation>
     <input>
       <soap:body use="literal" namespace="http://wwww.ippon.fr/sample/"> </ soap: body>
     </ Input>
     <output>
       <soap:body use="literal" namespace="http://wwww.ippon.fr/sample/"> </ soap: body>
     </ Output>
   </ Operation>
 </ Binding>
 <service name="sampleWebService">
   <port name="sampleWebServicePort" binding="tns:sampleWebServicePortBinding">
     <soap:address location="http://localhost:8080/sample-jax-ws-spring/services/sampleWebService"> </ soap: address>
   </ Port>
 </ Service>
 </ Definitions> 

The client

No need to develop specific classes to create a client, everything is done via a Spring configuration.

  <? 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.5.xsd">

 <bean id="sampleWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
   <property name="wsdlDocumentUrl" value="http://localhost:8080/sample-jax-ws-spring/services/sampleWebService?wsdl"/>
   <property name="namespaceUri" value="http://wwww.ippon.fr/sample/"/>
   <property name="serviceInterface" value="fr.ippon.sandbox.sample.ws.SampleWebService"/>
   <property name="serviceName" value="sampleWebService"/>
   <property name="portName" value="sampleWebServicePort"/>
 </ Bean>
 </ Beans> 

All parameters correspond to those declared in the file wsdl exposed.

To test the whole, we develop the following unit test:

  fr.ippon.sandbox.sample package;

 junit.framework.Assert import;

 org.apache.commons.logging.Log import;
 org.apache.commons.logging.LogFactory import;
 org.junit.Test import;
 org.springframework.beans.factory.annotation.Autowired import;
 org.springframework.beans.factory.annotation.Qualifier import;
 org.springframework.test.context.ContextConfiguration import;
 org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests import;

 fr.ippon.sandbox.sample.model.Sample import;
 fr.ippon.sandbox.sample.ws.SampleWebService import;

 @ ContextConfiguration (locations = {"classpath: / context / applicationContext-test.xml"})
 public class extends SampleWebServiceTest AbstractJUnit4SpringContextTests {

   private Log logger = LogFactory.getLog (getClass ());

    Autowired @ @ Qualifier ("sampleWebService")
    private SampleWebService sampleWebService;

    @ Test
    public void callWebService () {
     if (logger.isDebugEnabled ()) {
       logger.debug ("call web service");
     }
     Sample [] = sampleWebService.findSamples samples ("A code");

     Assert.assertNotNull ("The list Must Be not null", samples);
     Assert.assertTrue ("The list contains items must" samples.length> 0);

     if (logger.isDebugEnabled ()) {
       logger.debug (samples.length + "items.");
       for (Sample sample: samples) {
         logger.debug ("sample:" + sample.getCode () + "" + sample.getLabel () + "" + sample.getDate ());
       }
     }
   }
 } 

Remember to start the jetty server before running the unit test. But if you're a good engineer, you are lazy ... so to avoid starting jetty server first before running the unit test, it is necessary to modify the pom.xml as follows:

  ...
   <plugin>
     <groupId> org.apache.maven.plugins </ groupId>
     maven-surefire <artifactId>-plugin </ artifactId>
     <configuration>
       <excludes> <exclude> ** / * Test.java </ exclude> </ exclude>
     </ Configuration>
     <executions>
       <execution>
         <id> integration-tests </ id>
         <phase> integration-test </ phase>
         <goals> <goal> test </ goal> </ goals>
         <configuration>
           <skip> false </ skip>
           <excludes> <exclude> none </ exclude> </ exclude>
           <includes> <include> ** / * Test.java </ include> </ includes>
         </ Configuration>
       </ Execution>
     </ Executions>
   </ Plugin>

   <plugin>
     <groupId> org.mortbay.jetty </ groupId>
     <artifactId> maven-jetty-plugin </ artifactId>
     <version> 6.1.10 </ version>
     <configuration>
       <scanIntervalSeconds> 10 </ scanIntervalSeconds>
       <stopKey> STOP </ stopKey>
       <stopPort> 9999 </ stopPort>
     </ Configuration>
     <executions>
       <execution>
         <id> start-jetty </ id>
         <phase> pre-integration-test </ phase>
         <goals> <goal> run </ goal> </ goals>
         <configuration>
           <scanIntervalSeconds> 0 </ scanIntervalSeconds>
           <daemon> true </ daemon>
         </ Configuration>
       </ Execution>
       <execution>
         <id> stop-jetty </ id>
         <phase> post-integration-test </ phase>
         <goals> <goal> stop </ goal> </ goals>
       </ Execution>
     </ Executions>
 </ Plugin> 

Please find attached file all sources.

Although the use of JAX-WS we have done a great (web) service, he still had to slightly modify our model to transform the lists (java.util.List) in array of objects.

Blog to share I hope this post will help one or the one of you.

  • http://blog.astik.info Romain Gonord

    Very interesting article and very clear!

    Not being an expert, I ask myself the importance of the following annotation in the web server of the service:
    <code> SOAPBinding @ (style = Style.RPC, Use.LITERAL use =) </ code>

    If we remove this line, we can manipulate lists java without worry. In addition, all parameters of each service method are encapsulated in a container that can manipulate data potentially zero.

    • http://blog.resilient-it.com/ wek

      RPC literal is not recommended for web services based on SOAP. Document / literal is recommended by the W3C.