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

logo

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 independent of 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: “https://www.hascode.com/2010/09/creating-a-soap-service-using-jax-ws-annotations/[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.

webservice components

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
eclipse new maven project 1
eclipse new maven project 2
eclipse new maven project 3

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”create schema

    create schema 2
  • The final schema file looks like this

    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.hascode.com/bookService/1.0" targetNamespace="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"/>
        </sequence>
      </complexType>
      <complexType name="BookServiceResponseType">
        <sequence>
          <element name="book" type="tns:BookType" maxOccurs="unbounded" minOccurs="0"/>
        </sequence>
      </complexType>
      <element name="BookServiceResponse" type="tns:BookServiceResponseType">
     </element>
      <complexType name="BookType">
        <sequence>
          <element name="title" type="string" maxOccurs="1" minOccurs="1"/>
          <element name="author" type="string" maxOccurs="1" minOccurs="1"/>
          <element name="published" type="date" maxOccurs="1" minOccurs="1"/>
        </sequence>
      </complexType>
    </schema>
  • This is how the schema types look like in my schema editor

    schema book type
    schema book service request type
    schema book service response type

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 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/" name="bookService" targetNamespace="https://www.hascode.com/bookService/1.0">
      <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

    wsdl editor2

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

    <?xml version="1.0"?>
    <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

    webservices overview
  • 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

soapui plugin installation1
Figure 1. soapUI Eclipse Plugin Installation

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 soapUI add project

  • 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>
    soapUI new request 1
    Figure 2. Create a new request in soapUI
  • 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>
    soapUI create request step2
    Figure 3. SOAP Request result in soapUI

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 “https://www.hascode.com/2010/04/create-a-soap-client-using-the-jax-ws-maven-plugin/[Create a SOAP client using the JAX-WS Maven Plugin]“.

Tutorial Sources

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

git clone https://github.com/hascode/contract-first-webservice-tutorial.git

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.

    <?xml version="1.0"?>
    <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/>
                  </action>
                </pluginExecution>
              </pluginExecutions>
            </lifecycleMappingMetadata>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>