Contract-First Web-Services using JAX-WS, JAX-B, Maven and Eclipse

August 23rd, 2011 by

Using the contract-first approach to define a web service offers some advantages in contrast to the code-first approach.

In the following tutorial we’re going to take a look at some details of this approach and we’re going to implement a real SOAP service using JAX-WS, Maven and the Eclipse IDE.

Finally we’re going to run our service implementation on an embedded Jetty instance and we’re going to take a look at soapUI and how to test our service using this neat tool.


 

Prerequisites

For the following tutorial you’re will need to have the following software installed …

Contract-First vs Code-First

First some thoughts on the different approach in the development of a new webservice or simply said – what is the advantage of using contract-first?

  • It’s independant from your programming language used .. perhaps the server and client are created using different programming languages or the decision for a specific language has not been made yet
  • It is more descriptive than a generated WSDL file, you’re able to apply more restrictions and rules here that you otherwise needed to implement in some method in your concrete programming language
  • If the server and the client part are implemented by different teams, both sides are able to begin implementing quickly
  • So what’s the disadvantage of contract first? Creating a SOAP web-service using JAX-WS and some annotations it is reeeelly easy and done quick just as described in my following article: “Creating a SOAP Service using JAX-WS Annotations

The Goal

The goal of this tutorial is to create a SOAP web-service following the contract-first approach. This means we’re going to define the service interface using xml schema first and create stub classes from the WSDL afterwards.

In this tutorial we’re going to implement a book service that allows its clients to query for books.

Creating a new project

Like in the rest of my other tutorials we’re going to create a “mavenized” project first …

  • Create a new, simple Maven project in Eclipse
  • Otherwise execute the following line in your console
    mvn archetype:generate

Creating the schema

Now that we’ve got a project we’re going to define the XML schema for our web-service’s request and responses..

We’re using the Eclipse schema editor here – it’s really helpful to quickly create a schema.

  • We’re creating a  new schema file in src/main/resources/schema named bookService.xsd in Eclipse via “New > Other > XML Schema File”
  • The final schema file looks like this
    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="https://www.hascode.com/bookService/1.0" xmlns:tns="https://www.hascode.com/bookService/1.0" elementFormDefault="qualified">
     
     <element name="BookServiceRequest"
     type="tns:BookServiceRequestType">
     </element>
     
     <complexType name="BookServiceRequestType">
     <sequence>
     <element name="limit" type="int"></element>
     </sequence>
     </complexType>
     
     <complexType name="BookServiceResponseType">
     <sequence>
     <element name="book" type="tns:BookType" maxOccurs="unbounded" minOccurs="0"></element>
     </sequence>
     </complexType>
     
     <element name="BookServiceResponse"
     type="tns:BookServiceResponseType">
     </element>
     
     <complexType name="BookType">
     <sequence>
     <element name="title" type="string" maxOccurs="1" minOccurs="1"></element>
     <element name="author" type="string" maxOccurs="1" minOccurs="1"></element>
     <element name="published" type="date" maxOccurs="1" minOccurs="1"></element>
     </sequence>
     </complexType>
    </schema>
  • This is how the schema types look like in my schema editor

Creating the WSDL

Now that we have defined our DTO types and the SOAP request and response type, we’re going to create a valid WSDL file using the xml schema …

  • We’re creating a new file named bookService.wsdl in src/main/resources/wsdl
    <?xml version="1.0" encoding="UTF-8"?>
    <wsdl:definitions name="bookService"
     targetNamespace="https://www.hascode.com/bookService/1.0" xmlns:tns="https://www.hascode.com/bookService/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="https://www.hascode.com/bookService/1.0">
     <xsd:import schemaLocation="../schema/bookService.xsd"
     namespace="https://www.hascode.com/bookService/1.0" />
     </xsd:schema>
     </wsdl:types>
     <wsdl:message name="BookServiceRequest">
     <wsdl:part name="BookServiceRequest" element="tns:BookServiceRequest" />
     </wsdl:message>
     <wsdl:message name="BookServiceResponse">
     <wsdl:part name="BookServiceResponse" element="tns:BookServiceResponse" />
     </wsdl:message>
     <wsdl:portType name="BookServicePortType">
     <wsdl:operation name="fetchBooks">
     <wsdl:input name="BookServiceRequest" message="tns:BookServiceRequest" />
     <wsdl:output name="BookServiceResponse" message="tns:BookServiceResponse" />
     </wsdl:operation>
     </wsdl:portType>
     <wsdl:binding name="BookServiceBinding" type="tns:BookServicePortType">
     <soap:binding style="document"
     transport="http://schemas.xmlsoap.org/soap/http" />
     <wsdl:operation name="fetchBooks">
     <soap:operation style="document"
     soapAction="https://www.hascode.com/bookService/fetchBooks" />
     <wsdl:input name="BookServiceRequest">
     <soap:body use="literal" />
     </wsdl:input>
     <wsdl:output name="BookServiceResponse">
     <soap:body use="literal" />
     </wsdl:output>
     </wsdl:operation>
     </wsdl:binding>
     <wsdl:service name="BookService">
     <wsdl:port name="BookServicePort" binding="tns:BookServiceBinding">
     <soap:address location="/service/bookService" />
     </wsdl:port>
     </wsdl:service>
    </wsdl:definitions>
  • If you want to you may use Eclipse’s WSDL editor here – it looks like this

Creating the Stub Classes using JAX-WS and Maven

Now we need to create stub classes from the WSDL file above. Luckily there is the JAX-WS Maven plugin that allows us to generate these files and add them to the project’s sources …

  • First we’re adding dependencies for javaee-web and jax-ws and configure the jax-ws maven plugin in our pom.xml – my final version looks like this one
    <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/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.hascode.tutorial</groupId>
     <artifactId>ws</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>Book Lookup Web Service</name>
     <description>hasCode.com Contract-First-Webservices Tutorial</description>
     <packaging>war</packaging>
     
     <repositories>
     <repository>
     <id>java.net2</id>
     <name>Repository hosting the jee6 artifacts</name>
     <url>http://download.java.net/maven/2</url>
     </repository>
     </repositories>
     <pluginRepositories>
     <pluginRepository>
     <id>java.net2</id>
     <name>Repository hosting the jee6 artifacts</name>
     <url>http://download.java.net/maven/2</url>
     </pluginRepository>
     </pluginRepositories>
     <dependencies>
     <dependency>
     <groupId>javax</groupId>
     <artifactId>javaee-web-api</artifactId>
     <version>6.0</version>
     <scope>provided</scope>
     </dependency>
     <dependency>
     <groupId>com.sun.xml.ws</groupId>
     <artifactId>jaxws-rt</artifactId>
     <version>2.2</version>
     </dependency>
     </dependencies>
     <build>
     
     <plugins>
     <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>2.0.2</version>
     <configuration>
     <source>1.6</source>
     <target>1.6</target>
     </configuration>
     </plugin>
     <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.1.1</version>
     <configuration>
     <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
     </plugin>
     
     <plugin>
     <groupId>org.mortbay.jetty</groupId>
     <artifactId>jetty-maven-plugin</artifactId>
     </plugin>
     
     <plugin>
     <groupId>org.codehaus.mojo</groupId>
     <artifactId>build-helper-maven-plugin</artifactId>
     <executions>
     <execution>
     <id>add-source</id>
     <phase>generate-sources</phase>
     <goals>
     <goal>add-source</goal>
     </goals>
     <configuration>
     <sources>
     <source>${basedir}/target/generated/src/main/java</source>
     </sources>
     </configuration>
     </execution>
     </executions>
     </plugin>
     
     <plugin>
     <groupId>org.codehaus.mojo</groupId>
     <artifactId>jaxws-maven-plugin</artifactId>
     <version>1.12</version>
     <configuration>
     <wsdlDirectory>${basedir}/src/main/resources/wsdl</wsdlDirectory>
     <packageName>com.hascode.tutorial.ws.service</packageName>
     <keep>true</keep>
     <sourceDestDir>${basedir}/target/generated/src/main/java</sourceDestDir>
     </configuration>
     <executions>
     <execution>
     <goals>
     <goal>wsimport</goal>
     </goals>
     </execution>
     </executions>
     </plugin>
     </plugins>
     <finalName>bookService</finalName>
     </build>
    </project>
  • Now generate the sources from WSDL using
    mvn generate-sources
  • The generated classes are stored in target/generated .. as an example – my generated BookType class looks like this
    package com.hascode.ws.generated;
     
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlSchemaType;
    import javax.xml.bind.annotation.XmlType;
    import javax.xml.datatype.XMLGregorianCalendar;
     
    /**
     * <p>Java class for BookType complex type.
     *
     * <p>The following schema fragment specifies the expected content contained within this class.
     *
     * <pre>
     * &lt;complexType name="BookType">
     *   &lt;complexContent>
     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
     *       &lt;sequence>
     *         &lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/>
     *         &lt;element name="author" type="{http://www.w3.org/2001/XMLSchema}string"/>
     *         &lt;element name="published" type="{http://www.w3.org/2001/XMLSchema}date"/>
     *       &lt;/sequence>
     *     &lt;/restriction>
     *   &lt;/complexContent>
     * &lt;/complexType>
     * </pre>
     *
     *
     */
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "BookType", propOrder = {
     "title",
     "author",
     "published"
    })
    public class BookType {
     
     @XmlElement(required = true)
     protected String title;
     @XmlElement(required = true)
     protected String author;
     @XmlElement(required = true)
     @XmlSchemaType(name = "date")
     protected XMLGregorianCalendar published;
     
     [.. setter / getter ..]
    }

Writing the Server Implementation

In the next step we’re going to write our own implementation of the service interface ..

  • In the package com.hascode.tutorial.ws.service we’re creating our implementation of the generated service interface BookServicePortType in a class named BookServicePortTypeImpl
    package com.hascode.tutorial.ws.service;
     
    import java.util.GregorianCalendar;
     
    import javax.jws.WebService;
    import javax.xml.datatype.DatatypeConfigurationException;
    import javax.xml.datatype.DatatypeFactory;
     
    import com.hascode.ws.BookServicePortType;
    import com.hascode.ws.BookServiceRequestType;
    import com.hascode.ws.BookServiceResponseType;
    import com.hascode.ws.BookType;
     
    @WebService(endpointInterface="com.hascode.ws.service.BookServicePortType")
    public class BookServicePortImpl implements BookServicePortType {
     
     @Override
     public BookServiceResponseType fetchBooks(
     BookServiceRequestType bookServiceRequest) {
     final BookServiceResponseType response = new BookServiceResponseType();
     for(int i=0;i< bookServiceRequest.getLimit();i++){
     final BookType book = new BookType();
     book.setAuthor("Elvis "+i);
     try {
     book.setPublished(DatatypeFactory.newInstance()
     .newXMLGregorianCalendar(new GregorianCalendar(2011,8,14)));
     } catch (DatatypeConfigurationException e) {
     }
     book.setTitle("Programming Java Edition #"+i);
     response.getBook().add(book);
     }
     return response;
     }
    }
  • As you see, we’re going to add as much books to the response as the user told us using the limit parameter in the request

Creating the Server Instance

Now we want to query for some books but we need a server first ..

  • Create the directory src/main/webapp/WEB-INF if it does not exist yet
    mkdir -p src/main/webapp/WEB-INF
  • Add the following web.xml to the JAX-WS context listeners
    <?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_3_0.xsd"
     version="3.0">
     <session-config>
     <session-timeout>
     40
     </session-timeout>
     </session-config>
     <listener>
     <listener-class>
     com.sun.xml.ws.transport.http.servlet.WSServletContextListener
     </listener-class>
     </listener>
     <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>/bookService</url-pattern>
     </servlet-mapping>
    </web-app>
  • In the next step we’re adding a file named sun-jaxws.xml to specify the endpoint and the mapping to our implementation from the preceding step
    <?xml version="1.0" encoding="UTF-8"?>
    <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
    	version="2.0">
    	<endpoint name="bookService"
    		implementation="com.hascode.tutorial.ws.service.BookServicePortImpl"
    		url-pattern="/bookService" />
    </endpoints>
  • Now we’re starting an embedded Jetty instance to run our server instance
    mvn jetty:run
  • You should by now be able to get an overview of the running web-services by opening the following address in your browser http://localhost:8080/bookService
  • Our web-services WSDL is now delivered at the following address: http://localhost:8080/bookService?wsdl and looks like this
    <?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net.
    	RI's version is JAX-WS RI 2.2-12/14/2009 02:16 PM(ramkris)-. --><!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net.
    	RI's version is JAX-WS RI 2.2-12/14/2009 02:16 PM(ramkris)-. -->
    <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://service.ws.tutorial.hascode.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    	xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://service.ws.tutorial.hascode.com/"
    	name="BookServicePortImplService">
    	<import namespace="https://www.hascode.com/bookService/1.0"
    		location="http://localhost:8080/bookService?wsdl=1" />
    	<binding xmlns:ns1="https://www.hascode.com/bookService/1.0"
    		name="BookServicePortImplPortBinding" type="ns1:BookServicePortType">
    		<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
    			style="document" />
    		<operation name="fetchBooks">
    			<soap:operation soapAction="https://www.hascode.com/bookService/fetchBooks" />
    			<input>
    				<soap:body use="literal" />
    			</input>
    			<output>
    				<soap:body use="literal" />
    			</output>
    		</operation>
    	</binding>
    	<service name="BookServicePortImplService">
    		<port name="BookServicePortImplPort" binding="tns:BookServicePortImplPortBinding">
    			<soap:address location="http://localhost:8080/bookService" />
     
    		</port>
    	</service>
    </definitions>

Installing soapUI

soapUI is my favourite tool for testing and debugging SOAP web services. You may use it as a standalone app or as Eclipse plugin .. the last one is what I am going to use here ..

Install soapUi using the following update site http://www.soapui.org/eclipse/update

Testing the Web-Service

Now that we finally got a running web service, we’re going to test it. soapUI is my favourite tool for this …

  • Start soapUI, select File > New soapUI Project. Enter what you want as project name but use the URL from above as WSDL: http://localhost:8080/bookService?wsdl
  • As you can see, soapUI has detected the methods available .. to start a new request, simply double-click on fetchBooks > Request 1 in the left navigation and enter a value of 4 in the request’s xml so that it looks like this one:
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="https://www.hascode.com/bookService/1.0">
       <soapenv:Header/>
       <soapenv:Body>
          <ns:BookServiceRequest>
             <ns:limit>4</ns:limit>
          </ns:BookServiceRequest>
       </soapenv:Body>
    </soapenv:Envelope>

  • Now click on the green icon “Submit request to specified endpoint url” and enjoy the result delivered from our web-service
    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
       <S:Body>
          <BookServiceResponse xmlns="https://www.hascode.com/bookService/1.0">
             <book>
                <title>Programming Java Edition #0</title>
                <author>Elvis 0</author>
                <published>2011-09-14+02:00</published>
             </book>
             <book>
                <title>Programming Java Edition #1</title>
                <author>Elvis 1</author>
                <published>2011-09-14+02:00</published>
             </book>
             <book>
                <title>Programming Java Edition #2</title>
                <author>Elvis 2</author>
                <published>2011-09-14+02:00</published>
             </book>
             <book>
                <title>Programming Java Edition #3</title>
                <author>Elvis 3</author>
                <published>2011-09-14+02:00</published>
             </book>
          </BookServiceResponse>
       </S:Body>
    </S:Envelope>

Creating a SOAP client using the JAX-WS Maven Plugin

If you’d like to know how to create a SOAP client from the WSDL in a few minutes using JAX-WS, please take a look at my tutorial “Create a SOAP client using the JAX-WS Maven Plugin“.

Tutorial Sources

I have put the source from this tutorial on my Bitbucket repository – download it there or check it out using Mercurial:

hg clone https://bitbucket.org/hascode/contract-first-webservice-tutorial

Troubleshooting

  • “Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:jaxws-maven-plugin:1.12:wsimport (execution: default, phase: generate-sources) Maven Project Build Lifecycle Mapping Problem” – Downgrade your m2eclipse Plugin version or use the quick fix offered and tell m2eclipse shut up and ignore this “problem. Detailed information on this can be found on this wiki page.
    <pluginManagement>
     <plugins>
     <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
     <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
     <lifecycleMappingMetadata>
     <pluginExecutions>
     <pluginExecution>
     <pluginExecutionFilter>
     <groupId>
     org.codehaus.mojo
     </groupId>
     <artifactId>
     jaxws-maven-plugin
     </artifactId>
     <versionRange>
     [1.12,)
     </versionRange>
     <goals>
     <goal>wsimport</goal>
     </goals>
     </pluginExecutionFilter>
     <action>
     <ignore></ignore>
     </action>
     </pluginExecution>
     </pluginExecutions>
     </lifecycleMappingMetadata>
     </configuration>
     </plugin>
     </plugins>
     </pluginManagement>

Resources

Tags: , , , , , , , , , , , ,

4 Responses to “Contract-First Web-Services using JAX-WS, JAX-B, Maven and Eclipse”

  1. Bruno Says:

    Great Article!

    I’ve done your tutorial, but an error occurred. Until now I can’t solve. In pom, I put exactly like you, but this message is shown:

    Plugin execution not covered by lifecycle configuration: org.jvnet.jax-ws-commons:jaxws-maven-plugin:2.2:wsimport (execution: default, phase: generate-sources)

    Please! Could you help me?

  2. micha kops Says:

    Thanks and hi! :) I suppose that it might be a similar problem as described in the troubleshooting section due to lifecycle problems with the new m2eclipse plugin .. if you’re running the maven commands in the command line, everything is ok, right? if this is the case then just use the quick fix option to ignore the lifecycle phase for the m2eclipse plugin (similar to the case described in the article’s troubleshooting section).

    Please keep me updated if it did not work for you!

  3. Atef Says:

    Hello,
    I’m trying to make the generate-sources step using maven, but i keep bumping into errors:
    Failed to execute goal on project ws: Could not resolve dependencies for project com.hascode.tutorial:ws:war:0.0.1-SNAPSHOT: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/2 was cached in the local repository, resolution will not be reattempted until the update interval of java.net2 has elapsed or updates are forced

    I’m using Eclipse Luna and Maven 3.2.3, although i also attempted to do this on Eclipse Indigo with Maven 2.2.1, I just received a different error.
    Even when I imported your source code simply I had java errors with the BookServicePortType as it doesn’t recognize it for some reason.

  4. micha kops Says:

    Hi Atef,

    possibly a Maven download problem. Please remove the artifacts for the cached dependency from your local Maven repository (rm -rf ~/.m2/repository/com/sun/istack) and trigger the Maven build/download again.

Search
Tags
Categories