Developing a contract-first JAX-WS webservice

In this blog i’ll develop a simple webservice using JAX-WS. I’ll first start with the contract (wsdl and xsd’s). The contract will be used for generating the necessary JAXB artifacts. Getting the webservice up and running will be a piece of cake after that all thanks to Maven and JAX-WS.
First things first, let’s create a new Maven project. I’m using Netbeans as IDE for this.

New Maven Web Application

First Create a new Maven Web Application

and call it hello_person for example

The Contract

The webservice will accept a Person graph with a first and last name and will return a concatenated “Hello first name last name!”. Not very original but good enough for the example.
First we’ll define the xsd called helloPersonService.xsd for the request and response. The request will contain a Person object with a first and last name, the response will contain a Greetings string. We’ll store it in src/main/resources/xsd.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            xmlns="http://example.nl/hellopersonservice/1.0"
            targetNamespace="http://example.nl/hellopersonservice/1.0">
    <xsd:element name="HelloPersonServiceRequest" type="HelloPersonServiceRequestType"/>
    <xsd:element name="HelloPersonServiceResponse" type="HelloPersonServiceResponseType"/>

    <xsd:complexType name="HelloPersonServiceRequestType">
        <xsd:element name="Person" type="PersonType"/>
    </xsd:complexType>
    
    <xsd:complexType name="HelloPersonServiceResponseType">
        <xsd:element name="Greetings" type="xsd:string"/>
    </xsd:complexType>

    <xsd:complexType name="PersonType">
        <xsd:sequence>
            <xsd:element name="FirstName" type="xsd:string"/>
            <xsd:element name="LastName" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

Now we’ll define a wsdl called helloPersonService.wsdl and we’ll store it in src/main/resources/wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
    name="helloPersonService"
    targetNamespace="http://example.nl/hellopersonservice/1.0"
    xmlns:tns="http://example.nl/hellopersonservice/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://example.nl/hellopersonservice/1.0">
            <xsd:import schemaLocation="../xsd/helloPersonService.xsd"
                        namespace="http://example.nl/hellopersonservice/1.0"/>
        </xsd:schema>
    </wsdl:types>
    <wsdl:message name="HelloPersonServiceRequest">
        <wsdl:part name="HelloPersonServiceRequest" element="tns:HelloPersonServiceRequest"/>
    </wsdl:message>
    <wsdl:message name="HelloPersonServiceResponse">
        <wsdl:part name="HelloPersonServiceResponse" element="tns:HelloPersonServiceResponse"/>
    </wsdl:message>
    <wsdl:portType name="HelloPersonServicePortType">
        <wsdl:operation name="greetPerson">
            <wsdl:input name="HelloPersonServiceRequest" message="tns:HelloPersonServiceRequest"/>
            <wsdl:output name="HelloPersonServiceResponse" message="tns:HelloPersonServiceResponse"/>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="HelloPersonServiceBinding" type="tns:HelloPersonServicePortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <wsdl:operation name="greetPerson">
            <soap:operation style="document" soapAction="http://example.nl/HelloPersonService/greetPerson"/>
            <wsdl:input name="HelloPersonServiceRequest">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="HelloPersonServiceResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="HelloPersonService">
        <wsdl:port name="HelloPersonServicePort" binding="tns:HelloPersonServiceBinding">
            <soap:address location="/service/helloPersonService" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Getting the pom right

Next up is adding the necessary component to the pom file. We need the jax-ws runtime libraries, the jetty plugin as we use jetty as the servlet container, and the jaxws-maven-plugin for generating the java code from the contract. I’ve highlighted these dependencies in the pom file below.

<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>nl.example</groupId>
    <artifactId>hello_person</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>hello_person Java EE 6 Webapp</name>
    <url>http://maven.apache.org</url>
    <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>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.2</version>
            <scope>test</scope>
        </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-beta-1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <configuration>
                    <webAppConfig>
                        <contextPath>hello_person</contextPath>
                    </webAppConfig>
                    <connectors>
                        <connector implementation="org.eclipse.jetty.nio.SelectChannelConnector">
                            <port>8083</port>
                        </connector>
                    </connectors>
                </configuration>
            </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>nl.example.hello_person.service.generated</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>hello_person</finalName>
    </build>
</project>

Writing the implementation

First, let’s do a Clean and Build in NetBeans. This will generate the JAXB/JAX-WS artifacts from the contract.
Notice that the jaxws-maven-plugin has generated an interface called nl.example.hello_person.service.generated.HelloPersonServicePortType for the PortType in the wsdl. This is the interface we have to implement. The code for the implementation is pretty straightforward. I’ve added it below for convenience. Notice that the implementation class is called nl.example.hello_person.service.HelloPersonServiceImpl. We’ll need this in one of the next steps. Also notice the necessary @WebService annotation. The endpointInterface atribute points of course to the generated interface.

package nl.example.hello_person.service;

import javax.jws.WebService;
import nl.example.hello_person.service.generated.HelloPersonServiceRequestType;
import nl.example.hello_person.service.generated.HelloPersonServiceResponseType;

@WebService(endpointInterface="nl.example.hello_person.service.generated.HelloPersonServicePortType")
public class HelloPersonServiceImpl implements nl.example.hello_person.service.generated.HelloPersonServicePortType {

    @Override
    public HelloPersonServiceResponseType greetPerson(HelloPersonServiceRequestType helloPersonServiceRequest) {
        HelloPersonServiceResponseType helloPersonServiceResponse = new HelloPersonServiceResponseType();
        helloPersonServiceResponse.setGreetings("Hello " + helloPersonServiceRequest.getPerson().getFirstName() + " " + helloPersonServiceRequest.getPerson().getLastName() + "!");
        return helloPersonServiceResponse;
    }

}

The web.xml file

Now, add a web.xml file to the project (you can do this via New File > Web > Standard Deployment Descriptor (web.xml)) and add the proper configuration for the jax-ws servlet to it:

<?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>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <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>/helloPersonService</url-pattern>
    </servlet-mapping>
</web-app>

The sun-jaxws.xml file

The last step is adding the sun-jaxws.xml file to the WEB-INF directory of the project. This is the content of the file:

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
    xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'
    version='2.0'>
    <endpoint
        name='helloPersonService'
        implementation='nl.example.hello_person.service.HelloPersonServiceImpl'
        url-pattern='/helloPersonService' />
</endpoints>

Notice that the implementation attribute is pointing to our implementation class.

Running the service

Add jetty:run as custom action (via Run > Set Project Configuration > Customize > Actions) and run it.
Now open a browser and check the following url http://localhost:8083/hello_person/helloPersonService. If you see the following screen, the webservice is up and running!

Testing the service

For testing I’m using the soapUI plugin for Netbeans.
After you’ve installed the plugin, you can create a new Web Service Testing Project (New Project > SOA > Web Service Testing Project)

Point the initial WSDL property to the url you’ve seen on the Web Services page: http://localhost:8083/hello_person/helloPersonService?wsdl.

The testsuite now contains a greetPerson Test Step. You can now test if the service works. If all goes well, you should see something similar to the picture below.

18 thoughts on “Developing a contract-first JAX-WS webservice

  1. Nice example. Very clear and simple.

    Any ideas on the pom file configuration when the wsdl and xsd’s are somewhere out there on the internet, and you are behind a proxy?

    Enno.

    • Thanks Enno. I guess you could still get a hold on the wsdl and xsd’s and add them to the project locally. But that’s a bit like the other way around isn’t it? This example assumes that you’re building a new webservice from scratch and first build the contract. In your case the contract is already out there, so i’m guessing the service also is. Or are you implying some sort of central repository where you keep all your wsdl and xsd files?

      • Hi Roger,

        Maybe my question is/was out of scope for your example.

        I’m more thinking of building a/the client when the service is indeed already build and available. This should be possible by configuring the pom so it checks online for the wsdl and the xsd’s (whithout copying them to a local repository) and generating the needed classes. I thought there was a difference when trying this with or without a proxy server in between. Hence my question.

        Thanks,

        Enno.

      • Hi Enno,

        Check. I haven’t tried it myself, but according to the jaxws-maven plugin documentation there is a parameter called httpproxy available for setting a proxy. The wsimport goal also generates a client class. So if the httpproxy parameter works, you can point the wsdlUrls parameter to the external wsdl and i’m guessing you’re all set to go.

        Regards, Roger.

  2. Does starting jetty from the pom handle deploying the webservice to the jetty server?

    • Hello Chris,
      No it doesn’t. You have to run a mvn clean install or mvn clean package before firing up the jetty server via mvn jetty:run. Running jetty before packaging or installing the web service will amount to nothing. It will fire up Jetty but the web service won’t be deployed, hence won’t be available.
      Furthermore, the configuration in the example is pretty basic and it will not give you hot deployment on jetty. When you make a change to the web service, you’ll have to stop jetty first, then do a package or install and finally startup jetty again.
      For hot deployment you could check out Cargo. Apart from hot deployment features it’s also a good way to standardize JEE container management.

  3. Hi,

    This is really a good example to quickly grasp the concept of web services using JAX-WS. I have tried to run this example in both net beans and cmd but got the below error. Not sure how to solve this since am new to moven. If you could help on this issue, it would be great.

    Thanks in advance

    Thanks
    Sree Hari

    [ERROR] Failed to execute goal on project hello_person: Could not resolve dependencies for project com.ws:hello_person:war:1.0-SNAPSHOT: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced -> [Help 1]
    org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal on project hello_person: Could not resolve dependencies for project com.ws:hello_person:war:1.0-SNAPSHOT: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced
    at org.apache.maven.lifecycle.internal.LifecycleDependencyResolver.getDependencies(LifecycleDependencyResolver.java:196)
    at org.apache.maven.lifecycle.internal.LifecycleDependencyResolver.resolveProjectDependencies(LifecycleDependencyResolver.java:108)
    at org.apache.maven.lifecycle.internal.MojoExecutor.ensureDependenciesAreResolved(MojoExecutor.java:258)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:201)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:319)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
    Caused by: org.apache.maven.project.DependencyResolutionException: Could not resolve dependencies for project com.ws:hello_person:war:1.0-SNAPSHOT: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced
    at org.apache.maven.project.DefaultProjectDependenciesResolver.resolve(DefaultProjectDependenciesResolver.java:170)
    at org.apache.maven.lifecycle.internal.LifecycleDependencyResolver.getDependencies(LifecycleDependencyResolver.java:171)
    … 22 more
    Caused by: org.sonatype.aether.resolution.DependencyResolutionException: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced
    at org.sonatype.aether.impl.internal.DefaultRepositorySystem.resolveDependencies(DefaultRepositorySystem.java:412)
    at org.apache.maven.project.DefaultProjectDependenciesResolver.resolve(DefaultProjectDependenciesResolver.java:164)
    … 23 more
    Caused by: org.sonatype.aether.resolution.ArtifactResolutionException: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced
    at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:541)
    at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolveArtifacts(DefaultArtifactResolver.java:220)
    at org.sonatype.aether.impl.internal.DefaultRepositorySystem.resolveDependencies(DefaultRepositorySystem.java:395)
    … 24 more
    Caused by: org.sonatype.aether.transfer.ArtifactNotFoundException: Failure to find com.sun.istack:istack-commons-runtime:jar:1.1 in http://download.java.net/maven/1/ was cached in the local repository, resolution will not be reattempted until the update interval of maven-repository.dev.java.net has elapsed or updates are forced
    at org.sonatype.aether.impl.internal.DefaultUpdateCheckManager.checkArtifact(DefaultUpdateCheckManager.java:190)
    at org.sonatype.aether.impl.internal.DefaultArtifactResolver.resolve(DefaultArtifactResolver.java:430)
    … 26 more
    [ERROR]
    [ERROR]
    [ERROR] For more information about the errors and possible solutions, please read the following articles:
    [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException

    • Hi Sree,

      If I had to guess, i’d think you’re pom file is not entirely correct. Look for the repositories element. It should have a repository in it with an url pointing to http://download.java.net/maven/2.
      Looks like your pom file is looking in the repository located at http://download.java.net/maven/1. This repository doesn’t have the necessary jars needed for the example. To be more specific, it lacks the javaee-web-api and jaxws-rt dependency graphs. Look at the pom in the example for more details. Hope this helps.

      Regards, Roger.

    • I know this is old, just just posting for posterity.

      I ran into the same issue with istack-commons-runtime. I just had to explicitly add it as a dependency:

      com.sun.istack
      istack-commons-runtime
      1.1-SNAPSHOT
      provided

      hope this helps someone!

  4. Need some help on handling xsd:any .
    The xsd:any type is an XMLSchema element that defines elements that can
    contain undefined XML data.The application
    deals directly with XML data and do not want the overhead of
    converting the XML data into a Java object and then converting back into
    XML in to be inserted into a SOAP message.

    Need help on the implementation and also on handling the element. Also how to handling the xml data avaialble as String in the application to transfer its contents into a
    SOAPElement instance

    Pramod Kumar

Leave a comment