Java webservice example using Maven 3, JAX-WS wsimport, Eclipse

juni 27, 2013

Technical blog, describing how to make a java webservice, with a WSDL as the starting point. The discovery producing the flow in the solution, is dividing the project in two Maven 3 sibling projects.

Technologies used:

  • Maven 3
  • JAX-WS 2.2.8
  • Java Servlet API 2.5
  • Eclipse

References:

Links:

I have been having an urge to experiment with JAX-WS wsimport since my boss introduced me to the book “Java Web Services: Up and Running” by Martin Kalin.

I have grown to prefer to base my projects on build tools, instead of IDE’s, thus making the IDE an aside. For that reason I dumped several blogs and articles as inspiration, since they tended to lock in with a particular IDE.

However, I immediately saw potential in Roger Goossens blog on the issue, even though he makes more use of IDE magic, than I prefer to do. Inspired by Roger Goossens blog, I managed to get from a Wsdl of the same structure as Rogers, to generated Java artifacts pretty quickly, using Maven 3 – In particular the jaxws-maven-plugin. Roger made use of an older version, but I managed to update it, without too much fuss.  But then I was kind of stuck for a while, since I couldn’t find an easy way to include the generated code in my IDE project. Some sources on the web wrote about just accepting to copy the generated source into your project, but I didn’t feel that it was the right way to do it.

Then I saw an entry in a Stackoverflow.com thread by Michael Rutherfurd. He suggested to split the project in two Maven sibling projects, and thats what I decided to do.

As Michael sketched out in the answer, I divided my project in two, along the lines of generated code in one project, and the code utilizing the generated code in the other.

Since this project is stitched together using the creative products of so many bright and generous people, I thought that the least I could do, was to make the result available for the general public. That is – at least those members of the public who think that nerding with black screens with white letters is fun and rewarding. So – without further ado, here it is:

Project structure

Folderstructure containing sourcefiles, configuration files, buildfiles, etc.

Folderstructure containing sourcefiles, configuration files, buildfiles, etc.

Pom files

The parent pom

<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>info.martinussen.beerexpert</groupId>
  <artifactId>beerexpert-ws-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Beer Expert Webservice Parent</name>
  <url>http://maven.apache.org</url>

  <modules>
    <module>beerexpert-ws-jar</module>
    <module>beerexpert-ws-war</module>
  </modules>

  <properties>
    <!-- this is to prevent the build from being platform dependent" -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.6</java.version>
    <log4j.version>1.2.17</log4j.version>
    <junit.version>4.11</junit.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
      </dependency>

      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <finalName>beerexpert-ws</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.0</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <version>2.9</version>
        <configuration>
          <downloadSources>true</downloadSources>
          <downloadJavadocs>true</downloadJavadocs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The jar project pom

Notice the “endorsed” construction – without it I experienced an exception along the lines of “Missing Constructor argument”

<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>
  <parent>
    <groupId>info.martinussen.beerexpert</groupId>
    <artifactId>beerexpert-ws-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>beerexpert-ws-jar</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Beer Expert Webservice jax-ws generated artifacts</name>

  <!-- https://jax-ws.java.net/2.2.8/docs/ -->
  <!-- http://rphgoossens.wordpress.com/2011/02/20/developing-a-contract-first-jax-ws-webservice/ -->
  <dependencies>
    <dependency>
      <groupId>com.sun.xml.ws</groupId>
      <artifactId>jaxws-rt</artifactId>
      <version>2.2.8</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>beerexpert-ws-jar</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.0</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>UTF-8</encoding>
          <!-- https://jax-ws-commons.java.net/jaxws-maven-plugin/usage.html -->
          <compilerArguments>
            <endorseddirs>${project.build.directory}/endorsed</endorseddirs>
          </compilerArguments>
        </configuration>
      </plugin>

      <!-- https://jax-ws-commons.java.net/jaxws-maven-plugin/usage.html -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.3</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <outputDirectory>${project.build.directory}/endorsed</outputDirectory>
              <silent>true</silent>
              <artifactItems>
                <artifactItem>
                  <groupId>javax.xml.bind</groupId>
                  <artifactId>jaxb-api</artifactId>
                  <version>2.2.7</version>
                  <type>jar</type>
                </artifactItem>
                <artifactItem>
                  <groupId>javax.xml.ws</groupId>
                  <artifactId>jaxws-api</artifactId>
                  <version>2.2.9</version>
                  <type>jar</type>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
			
      <!-- https://jax-ws-commons.java.net/jaxws-maven-plugin/ -->
      <plugin>
        <groupId>org.jvnet.jax-ws-commons</groupId>
        <artifactId>jaxws-maven-plugin</artifactId>
        <version>2.3</version>
        <executions>
          <execution>
            <goals>
              <goal>wsimport</goal>
            </goals>
            <configuration>
              <wsdlDirectory>${basedir}/src/main/resources/wsdl</wsdlDirectory>
              <packageName>info.martinussen.beerexpert.service.generated</packageName>
              <sourceDestDir>${basedir}/target/generated-sources/main/java</sourceDestDir>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

the war project pom

More on the Jetty- and the failsafe plug-ins later…

<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>

  <parent>
    <groupId>info.martinussen.beerexpert</groupId>
    <artifactId>beerexpert-ws-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>beerexpert-ws-war</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Beer Expert Webservice implementation</name>

  <properties>
    <servlet.api.version>2.5</servlet.api.version>
    <jetty.version>6.1.26</jetty.version>
    <jetty.port>8081</jetty.port>
  </properties>

  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>${servlet.api.version}</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>info.martinussen.beerexpert</groupId>
      <artifactId>beerexpert-ws-jar</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>beerexpert-ws-implementation</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <version>2.9</version>
        <configuration>
          <wtpversion>2.0</wtpversion><!-- In order to make it an Eclipse 
            dynamic web project(war), as opposed to an Eclipse java(jar) project -->
          <downloadSources>true</downloadSources>
          <downloadJavadocs>true</downloadJavadocs>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>${jetty.version}</version>
        <configuration>
          <stopPort>9966</stopPort>
          <stopKey>Stop</stopKey>
          <connectors>
            <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
              <port>${jetty.port}</port>
            </connector>
          </connectors>
        </configuration>
        <executions>
          <execution>
            <!-- Runs Jetty in the pre-integration-test maven phase -->
            <id>start-jetty</id>
            <phase>pre-integration-test</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <daemon>true</daemon>
            </configuration>
          </execution>
          <execution>
            <!-- Stops Jetty in the post-integration-test maven phase -->
            <id>stop-jetty</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.6</version>
        <executions>
          <execution>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Wsdl and Schema

The wsdl – a combination of the interface of the BeerExpert model from “Head first Servlets and JSP, 2nd ed.” chapter 4 and the structure of Roger Goossens’ “Hello World” wsdl.

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="beerExpertService"
  targetNamespace="http://martinussen.info/beerexpertservice/1.0"
  xmlns:tns="http://martinussen.info/beerexpertservice/1.0" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  
  <wsdl:types>
    <xsd:schema targetNamespace="http://martinussen.info/beerexpertservice/1.0">
      <xsd:import schemaLocation="../xsd/beerExpertService.xsd"
        namespace="http://martinussen.info/beerexpertservice/1.0" />
    </xsd:schema>
  </wsdl:types>
  
  <wsdl:message name="BeerExpertServiceRequest">
    <wsdl:part name="BeerExpertServiceRequest" element="tns:BeerExpertServiceRequest" />
  </wsdl:message>
  
  <wsdl:message name="BeerExpertServiceResponse">
    <wsdl:part name="BeerExpertServiceResponse" element="tns:BeerExpertServiceResponse" />
  </wsdl:message>
  
  <wsdl:portType name="BeerExpertServicePortType">
    <wsdl:operation name="getBeerAdvise">
      <wsdl:input name="BeerExpertServiceRequest" message="tns:BeerExpertServiceRequest" />
      <wsdl:output name="BeerExpertServiceResponse" message="tns:BeerExpertServiceResponse" />
    </wsdl:operation>
  </wsdl:portType>
  
  <wsdl:binding name="BeerExpertServiceBinding" type="tns:BeerExpertServicePortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="getBeerAdvise">
      <soap:operation style="document" soapAction="http://martinussen.info/BeerAdviseService/getBeerAdvise" />
      <wsdl:input name="BeerExpertServiceRequest">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="BeerExpertServiceResponse">
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  
  <wsdl:service name="BeerExpertService">
    <wsdl:port name="BeerExpertServicePort" binding="tns:BeerExpertServiceBinding">
      <soap:address location="/service/beerExpertService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Schema

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   elementFormDefault="qualified" 
                xmlns="http://martinussen.info/beerexpertservice/1.0"
      targetNamespace="http://martinussen.info/beerexpertservice/1.0">
  
  <xsd:element name="BeerExpertServiceRequest" type="BeerExpertServiceRequestType" />
  <xsd:element name="BeerExpertServiceResponse" type="BeerExpertServiceResponseType" />

  <xsd:complexType name="BeerExpertServiceRequestType">
    <xsd:sequence minOccurs="1" maxOccurs="1">
      <xsd:element name="Color" type="xsd:string" />
    </xsd:sequence>
  </xsd:complexType>

  <xsd:complexType name="BeerExpertServiceResponseType">
    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="Advice" type="xsd:string" />
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

And we are ready to generate Jax-Ws portable artifacts…

In the parent pom folder, we tell maven to generate sources, compile them into class files, package the result into a jar- and war file respectively and install it in the local maven repository with the command mvn install
The resulting files in the beerexpert-ws-jar project are arranged like this:

The result from generating sources from the wsdl, compiling it into .class files and packing them in a .jar file.

The result from generating sources from the wsdl, compiling it into .class files and packing them in a .jar file.

Implementation

Now generate eclipse project files by issuing the command mvn eclipse:eclipse at the parent pom level. Import the two resulting eclipse projects into eclipse and add a new class (BeerExpertServiceImpl) in the beerexpert-ws-war project. Be sure to enter the name of the interface which the class is implementing (BeerExpertServicePortType) in the “new Class” dialog, thus making it possible for eclipse to provide you with a “Fill-in-the-blanks” getBeerAdvise method. Now implement the service getBeerAdvise method using the BeerExpert model class.

package info.martinussen.beerexpert.service;

import info.martinussen.beerexpert.model.BeerExpert;
import info.martinussen.beerexpert.service.generated.BeerExpertServicePortType;
import info.martinussen.beerexpert.service.generated.BeerExpertServiceRequestType;
import info.martinussen.beerexpert.service.generated.BeerExpertServiceResponseType;

import java.util.List;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

//http://rphgoossens.wordpress.com/2011/02/20/developing-a-contract-first-jax-ws-webservice/
@WebService(endpointInterface="info.martinussen.beerexpert.service.generated.BeerExpertServicePortType")
public class BeerExpertServiceImpl implements BeerExpertServicePortType {

  @Override
  @WebMethod(action = "http://martinussen.info/BeerAdviseService/getBeerAdvise")
  @WebResult(name = "BeerExpertServiceResponse",
             targetNamespace = "http://martinussen.info/beerexpertservice/1.0",
             partName = "BeerExpertServiceResponse")
  public BeerExpertServiceResponseType getBeerAdvise(
              @WebParam(name = "BeerExpertServiceRequest",
                        targetNamespace = "http://martinussen.info/beerexpertservice/1.0",
                        partName = "BeerExpertServiceRequest")
              BeerExpertServiceRequestType beerExpertServiceRequest) {

    String color = beerExpertServiceRequest.getColor();
    BeerExpert beerExpert = new BeerExpert();
    List advice = beerExpert.getBrands(color);

    BeerExpertServiceResponseType response = new BeerExpertServiceResponseType();

    for (String element : advice){
      response.getAdvice().add(element);
    }

    return response;
  }
}

Configuration is needed

Two configuration files are needed;

  • A deployment descriptor (web.xml) configuring WSServlet and WSServletContextListener
  • A sun-jaxws.xml detailing the service endpoint.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	       version="2.5">
  <display-name>Beer Expert Web Application</display-name>
	
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  	
  <!-- http://rphgoossens.wordpress.com/2011/02/20/developing-a-contract-first-jax-ws-webservice/ -->
  <servlet>
    <description>JAX-WS endpoint</description>
    <display-name>The JAX-WS servlet</display-name>
    <servlet-name>jaxws</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>jaxws</servlet-name>
    <url-pattern>/beerExpertService</url-pattern>
  </servlet-mapping>
  
  <listener>
    <listener-class>
      com.sun.xml.ws.transport.http.servlet.WSServletContextListener
    </listener-class>
  </listener>
</web-app>

sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'
  version='2.0'>
  
  <!-- http://rphgoossens.wordpress.com/2011/02/20/developing-a-contract-first-jax-ws-webservice/ -->
  <endpoint name='beerExpertService'
            implementation='info.martinussen.beerexpert.service.BeerExpertServiceImpl'
            url-pattern='/beerExpertService' />
</endpoints>

First test

mvn jetty:run in the beerexpert-ws-war folder launches the jetty servlet container and deploys the war file in it.
Point your favourite browser towards the url
http://localhost:8081/beerexpert-ws-war/beerExpertService and with a little luck you should see something like this:
BeerExpertService home page
Note the link to the wsdl, with that you can start a SoapUI test suite, if you like. I like to run tests automatically as part of the build. that’s why I made a rudimentary testclient, inspired by the article “JAX-WS Hello World Example – RPC Style ” by the always concise and to the point MKYoung.

package info.martinussen.beerexpert.service;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import info.martinussen.beerexpert.service.generated.*;

/**
 * Test client
 * http://www.mkyong.com/webservices/jax-ws/jax-ws-hello-world-example/
 */
public class BeerExpertServiceClient {

  public static final String WSDL_URL = "http://localhost:8081/beerexpert-ws-war/beerExpertService?wsdl";
  public static final String SERVICE_URI = "http://service.beerexpert.martinussen.info/";
  public static final String SERVICE_NAME = "BeerExpertServiceImplService";

  BeerExpertServicePortType port = null;

  public BeerExpertServiceClient(){
    URL url = null;
    try {
      url = new URL(WSDL_URL);
    } catch (MalformedURLException e) {
      throw new RuntimeException(e); // rethrow as rte
    }
    QName qname = new QName(SERVICE_URI, SERVICE_NAME);
    Service service = Service.create(url, qname);
    port = service.getPort(BeerExpertServicePortType.class);
  }

  public List getBrands(String color){
    BeerExpertServiceRequestType request = new BeerExpertServiceRequestType();
    request.setColor(color);
    BeerExpertServiceResponseType response = port.getBeerAdvise(request);
    return response.getAdvice();
  }
}

By means of the naming conventions of the failsafe maven plugin, and the automatic start and stop of the jetty server in the pre-integration-test and the post-integration-test phases, the parameterized JUnit integration test ClientITCase.java is run by issuing the command mvn verify – Thus testing all the way from client implementation, across the generated client- and service jax-ws portable artifacts, the WSServlet, the BeerExpert model class and back.

package info.martinussen.beerexpert.service;

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * Integration test running in the integration-test phase of the maven build
 * Tests that the service implementation returns correct results for the
 * same input parameters as the unit test of BeerExpert.
 * The BeerExpertServiceClient is used in the test
 *
 */
@RunWith(value = Parameterized.class)
public class ClientITCase {

 private BeerExpertServiceClient testClient = null;

  private String testColor;
  private String expected1;
  private String expected2;

  @Parameters
  public static Collection<String[]> getTestParameters(){
    return Arrays.asList(new String[][]{
        {"amber", "Jack Amber", "Red Moose"}, // value, expected1, expected2
        {"light", "Jail Pale Ale", "Gout Stout"},
        {"brown", "Jail Pale Ale", "Gout Stout"},
        {"dark",  "Jail Pale Ale", "Gout Stout"}
    });
  }

  public ClientITCase(String testColor, String expected1, String expected2){
    this.testColor = testColor;
    this.expected1 = expected1;
    this.expected2 = expected2;
  }

  @Before
  public void setUp() throws Exception {
    testClient = new BeerExpertServiceClient();
  }

  @After
  public void tearDown() throws Exception {
    testClient = null;
  }

  /**
   * Runs once for each item in the testParameters collection
   */
  @Test
  public void testGetBrandsForTestParameters() {
    List result = testClient.getBrands(testColor);
    assertEquals(2, result.size());
    assertTrue(result.contains(expected1));
    assertTrue(result.contains(expected2));
  }

}

Wrap-up

So – now I have given to the next person, the project I would have liked to let myself be inspired by, when I set out to explore Jax-Ws wsimport. I hope it will come in handy for someone.
Find the complete source by following the link to my bitbucket account in the start of the blog.

Reklamer