Wiki Blog Gallery

Creating a Web Service using Java 6

Introduction

In this article we shortly describe how to setup Web Service using JAX-WS technology and maven. We will create a client project, a service project and a shared common project (which contains JAX-WS generated classes). The projects' directory structure will be:

  • my-common/
    • src/
    • wsdl/
    • jaxws/
    • pom.xml
  • my-service
    • src/
    • webapp/
    • pom.xml
  • my-client
    • src/
    • webapp/
    • pom.xml

Starting from XSD/WSDL: using wsimport

Actually, when I've started the project, I have realised that using wsgenWSDLwsimport scheme is a bit complicated and excessive: you will be generated the same set of JAXB beans both for server and client, which you might want to share between Web Service and Java Web Client. So what we can do:

  • Create a Java class (the implementation of my future Web Service) with all methods, input/output arguments classes and exceptions defined. The implementation should be marked with @WebService annotation.
  • Run wsgen for it:
    [INFO] Scanning for projects...
    [INFO] ------------------------------------------------------------------------
    [INFO] Building My DB Service
    [INFO]    task-segment: [clean, install]
    [INFO] ------------------------------------------------------------------------
    [INFO] [resources:resources]
    ...
    [INFO] [compiler:compile]
    [INFO] Compiling 4 source files to my-db-service\target\classes
    [INFO] [jaxws:wsgen {execution: default}]
    Note:   ap round: 1
    [ProcessedMethods Class: org.mycompany.service.db.service.MyDBServiceImpl]
    [should process method: addConcept hasWebMethods: true ]
    [endpointReferencesInterface: false]
    [declaring class has WebSevice: true]
    [returning: true]
    [WrapperGen - method: addConcept(java.lang.String,java.lang.String,org.mycompany.service.db.service.ConceptType)]
    [method.getDeclaringType(): org.mycompany.service.db.service.MyDBServiceImpl]
    [requestWrapper: org.mycompany.service.db.service.jaxws.AddConcept]
    [ProcessedMethods Class: java.lang.Object]
    org\mycompany\service\db\service\jaxws\AddConcept.java
    org\mycompany\service\db\service\jaxws\AddConceptResponse.java
    org\mycompany\service\db\service\jaxws\MyDBServiceExceptionBean.java
    Note:   ap round: 2
    [INFO] [war:war]
    ...
  • Modify the resulting WSDL/XSD as necessary.

Due to limitation, that JSR-181 service endpoint scanner, implemented by Sun, does not implement merging of annotation properties of implementation and interface, we have to duplicate some of information in @WebService annotation of implementation class (in particular, add targetNamespace and mention endpointInterface).

MyDBServiceImpl.java

package org.mycompany.service.db.service;
 
import java.util.Arrays;
import java.util.Date;
 
import javax.jws.WebService;
 
import org.mycompany.service.Concept;
import org.mycompany.service.ConceptType;
import org.mycompany.service.db.jaxws.MyDBService;
import org.mycompany.service.db.jaxws.MyDBServiceException;
 
@WebService(name = "MyDBService", endpointInterface = "org.mycompany.service.db.jaxws.MyDBService", targetNamespace = "http://service.mycompany.org/")
public class MyDBServiceImpl implements MyDBService {
 
    /**
     * This method simply creates a new {@link Concept} instance based on passed arguments.
     */
    @Override
    public Concept addConcept(String id, String creator, ConceptType type) throws MyDBServiceException {
        return new Concept(id, new Date(), new Concept.Types(Arrays.asList(type)), creator);
    }
}

Now we are ready to do the reverse thing: generate beans and Web Service interface, complete the implementation and write a sample client for the service. In order to automate wsimport process we will use JAX-WS Maven Plugin.

pom.xml

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>wsimport</goal>
            </goals>
            <configuration>
                <sourceDestDir>${project.build.sourceDirectory}</sourceDestDir>
                <keep>true</keep>
                <!-- verbose>true</verbose -->
                <wsdlDirectory>wsdl</wsdlDirectory>
                <wsdlFiles>
                    <wsdlFile>MyDBService.wsdl</wsdlFile>
                </wsdlFiles>
            </configuration>
        </execution>
    </executions>
</plugin>

After execution we see:

> mvn install

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building My DB Common
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [jaxws:wsimport {execution: default}]
[INFO] Processing: wsdl\MyDBService.wsdl
...
parsing WSDL...

generating code...

org\mycompany\service\AddConcept.java
org\mycompany\service\AddConceptResponse.java
org\mycompany\service\Concept.java
org\mycompany\service\ConceptType.java
org\mycompany\service\MyDBImplService.java
org\mycompany\service\MyDBService.java
org\mycompany\service\MyDBServiceException.java
org\mycompany\service\MyDBServiceException_Exception.java
org\mycompany\service\ObjectFactory.java
org\mycompany\service\package-info.java
...

Using JAXB customizations

OK, what we get as the result:

  • Both JAXB and JAX-WS entities are generated in the same package, which is, by default, constructed from a target namespace of our service (targetNamespace="http://service.mycompany.org/"). This is sometimes not handy, as we want to separate them. Also this causes a minor name collision for the class MyDBServiceException, which is from one side JAXB bean for serialization of this exception (and is not a real Exception) and JAX-WS entity ugly called MyDBServiceException_Exception which is used in throws statements of service interface methods (and is a real Exception). We can separate packages for JAXB and JAX-WS in order to resolve this collision implicitly. For this we will add the following to jaxws/jaxws-binding.xml:

    jaxws/jaxws-binding.xml

    <jaxws:bindings version="2.0"
        xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        wsdlLocation="../wsdl/MyDBService.wsdl" >
     
        <jaxws:package name="org.mycompany.service.db.jaxws" />
    </jaxws:bindings>

    jaxws/jaxb-binding.xml

    <jaxb:bindings version="1.0"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        schemaLocation="../wsdl/MyDBService.xsd"
        node="/xsd:schema">
     
        <jaxb:schemaBindings>
            <jaxb:package name="org.mycompany.service.db.jaxb" />
        </jaxb:schemaBindings>
    </jaxb:bindings>
  • The XSD type xsd:dateTime is represented with class javax.xml.datatype.XMLGregorianCalendar, which you might wish to change to java.util.Date1). To make a trick we need to create a helper class with static methods that actually makes a conversion2):

    XSDateTimeCustomBinder.java

    package org.mycompany.service.db.jaxb;
     
    import java.util.Calendar;
    import java.util.Date;
    import java.util.GregorianCalendar;
     
    import javax.xml.bind.DatatypeConverter;
     
    public class XSDateTimeCustomBinder {
     
        public static Date parseDateTime(String dateTime) {
            return DatatypeConverter.parseDate(dateTime).getTime();
        }
     
        public static String printDateTime(Date date) {
            final Calendar cal = new GregorianCalendar();
     
            cal.setTime(date);
     
            return DatatypeConverter.printDateTime(cal);
        }
    }

    and use this class in JAXB javaType customization in jaxws/jaxb-binding.xml:

    <jaxb:bindings version="1.0" ...>
        <jaxb:globalBindings>
            <jaxb:javaType
                name="java.util.Date"
                xmlType="xsd:dateTime"
                parseMethod="org.mycompany.service.db.jaxb.XSDateTimeCustomBinder.parseDateTime"
                printMethod="org.mycompany.service.db.jaxb.XSDateTimeCustomBinder.printDateTime" />
        </jaxb:globalBindings>
    </jaxb:bindings>

Unfortunately customization of package and class name for XML adapters is not yet supported (see issue#611 and issue#506) but the workaround is described here. Also at least in JAXB v2.1.13 there is a problem if your adapter would like to throw an exception (should be thrown also by XmlAdapter implementation), see issue#780.

The complete file looks like this3):

jaxws/jaxb-binding.xml

<jaxb:bindings version="1.0"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    schemaLocation="../wsdl/MyDBService.xsd"
    node="/xsd:schema">
 
    <jaxb:globalBindings>
        <jaxb:javaType
            name="java.util.Date"
            xmlType="xsd:dateTime"
            parseMethod="org.mycompany.service.db.jaxb.XSDateTimeCustomBinder.parseDateTime"
            printMethod="org.mycompany.service.db.jaxb.XSDateTimeCustomBinder.printDateTime" />
    </jaxb:globalBindings>
    <jaxb:schemaBindings>
        <jaxb:package name="org.mycompany.service.db.jaxb" />
    </jaxb:schemaBindings>
</jaxb:bindings>

WSDL/XSD location is given as a relative path to JAX-WS/JAXB customization file, e.g. for given file jaxws/jaxb-binding.xml and XSD location wsdl/MyDBService.xsd the relative location will be ../wsdl/MyDBService.xsd.

Also we need to mention it in pom.xml:

pom.xml

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>1.12</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>wsimport</goal>
            </goals>
            <configuration>
                ...
                <bindingDirectory>jaxws</bindingDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

In prior to v1.12 of jaxws-maven-plugin there was a problem with javaType customization: if fails to compile XML adapter as it does not include XSDateTimeCustomBinder class into compilation set.

Further customizations involve JAXB2 plugins. These plugins hook into generation process and benefit from possibility to manipulate JAXB model to generate more programmer-friendly code4). Unfortunately, due to issue#45 it is not possible to inject JAXB plugins into JAX-WS using jaxws-maven-plugin. Also due to some conflicts with build-in Java 6 JAXB API, JAXB plugins do not work (see here). As a workaround I have reallocated most popular plugins to com.sun.tools.xjc.addon package.

The complete JAX-WS maven plugin + JAXB plugins with fake version 1.12.3 can be downloaded from here5) and installed via mvn install:install-file -Dfile=jaxws-maven-plugin-1.12.3.jar -DpomFile=jaxws-maven-plugin-1.12.3.pom -DgroupId=org.codehaus.mojo -DartifactId=jaxws-maven-plugin -Dversion=1.12.3 -Dpackaging=jar.

The version 1.12.4 has Xew plugin excluded as it was forked into separate project. Refer that config as example.

Now you can include it into your pom.xml:

pom.xml

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>1.12.3</version>
    <executions>
        <execution>
            ...
            <configuration>
                <xjcArgs>
                    <xjcArg>-Xxew</xjcArg>
                    <xjcArg>-instantiate early</xjcArg>
                    <xjcArg>-delete</xjcArg>
                    <xjcArg>-Xcommons-lang</xjcArg>
                    <xjcArg>-Xcommons-lang:ToStringStyle=SHORT_PREFIX_STYLE</xjcArg>
                    <xjcArg>-Xdefault-value</xjcArg>
                    <xjcArg>-Xfluent-api</xjcArg>
                    <xjcArg>-Xvalue-constructor</xjcArg>
                </xjcArgs>
            </configuration>
        </execution>
    </executions>
</plugin>

-Xcommons-lang should be mentioned twice: once to activate the plugin and another time to pass the plugin the actual arguments.

After re-running code generation we get better possibilities to code:

/*
 * This class is not compilable after -Xxew is applied.
 */
/*
public static Concept createDummyConcept_Normal() {
    final Concept concept = new Concept();
    final Concept.Types types = new Concept.Types();
 
    concept.setCreator("robot");
    concept.setCreationDateTime(new Date());
    concept.setTypes(types);
 
    types.getType().add(ConceptType.CREATURE);
    types.getType().add(ConceptType.HUMAN);
    types.getType().add(ConceptType.WOMAN);
 
    return concept;
}
 */
 
public static Concept createDummyConcept_ListAccess() {
    final Concept concept = new Concept();
 
    concept.setCreator("computer");
    concept.setCreationDateTime(new Date());
 
    // -Xxew demo:
    concept.getTypes().add(ConceptType.CREATURE);
    concept.getTypes().add(ConceptType.HUMAN);
    concept.getTypes().add(ConceptType.WOMAN);
 
    return concept;
}
 
public static Concept createDummyConcept_Construtor() {
    // -Xvalue-constructor demo:
    return new Concept("99", new Date(), "neighbour", Arrays.asList(ConceptType.CREATURE, ConceptType.HUMAN,
            ConceptType.WOMAN));
}
 
public static Concept createDummyConcept_FluentAPI() {
    // -Xfluent-api demo:
    return new Concept().withId("01").withCreationDateTime(new Date()).withCreator("author").withTypes(
            ConceptType.CREATURE, ConceptType.CHILD);
}

Further improvement comes from the fact that staring from v2.1.4 of JAX-WS RI it is not necessary to run wsgen to generate wrapper classes for input/output arguments of webservice methods6). So all we need is to generate JAXB beans for our datatypes XSD. We will also suppress generation of file headers with timestamp information, as it makes life with source version control easier. That can be done with the help of the following pom.xml:

pom.xml

<project ...>
    ...
    <dependencies>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.4</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.7.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <!--verbose>true</verbose-->
                            <generateDirectory>${project.build.sourceDirectory}</generateDirectory>
                            <schemaDirectory>xsd</schemaDirectory>
                            <bindingDirectory>jaxb</bindingDirectory>
                            <removeOldOutput>false</removeOldOutput>
                            <forceRegenerate>false</forceRegenerate>
 
                            <extension>true</extension>
                            <args>
                                <arg>-no-header</arg> <!-- suppress generation of a file header with timestamp -->
                                <arg>-Xxew</arg>
                                <arg>-instantiate early</arg>
                                <arg>-delete</arg>
                                <arg>-Xcommons-lang</arg>
                                <arg>-Xcommons-lang:ToStringStyle=SHORT_PREFIX_STYLE</arg>
                                <arg>-Xdefault-value</arg>
                                <arg>-Xfluent-api</arg>
                                <arg>-Xvalue-constructor</arg>
                            </args>
                            <plugins>
                                <plugin>
                                    <groupId>dk.conspicio.jaxb.plugins</groupId>
                                    <artifactId>jaxb-xew-plugin</artifactId>
                                    <version>1.3</version>
                                </plugin>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons.tools.xjc.plugin</groupId>
                                    <artifactId>jaxb-commons-lang-plugin</artifactId>
                                    <version>1.0</version>
                                </plugin>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons.tools.xjc.plugin</groupId>
                                    <artifactId>jaxb-default-value-plugin</artifactId>
                                    <version>1.0</version>
                                </plugin>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons.tools.xjc.plugin</groupId>
                                    <artifactId>jaxb-fluent-api-plugin</artifactId>
                                    <version>2.1.8</version>
                                </plugin>
                                <plugin>
                                    <groupId>org.jvnet.jaxb2_commons.tools.xjc.plugin</groupId>
                                    <artifactId>jaxb-value-constructor-plugin</artifactId>
                                    <version>1.0</version>
                                </plugin>
                                <plugin>
                                    <groupId>commons-lang</groupId>
                                    <artifactId>commons-lang</artifactId>
                                    <version>2.4</version>
                                </plugin>
                            </plugins>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
    <pluginRepositories>
        <pluginRepository>
            <id>maven2-repository.dev.java.net</id>
            <url>http://download.java.net/maven/2/</url>
        </pluginRepository>
    </pluginRepositories>
 
</project>

Notes:

  • Due to issue#697 commons-lang should be mentioned in <plugins> as well as in <dependencies> section.
  • All JAXB plugins are not available in Maven public replository, so you have to install them manually using fake groupId and artifactId.
  • jaxb-xew-plugin should be triggered first, as it changes the class model. Otherwise you'll get compilation problems in methods generated before it removes the unnecessary classes.

Deployment to tomcat server

First, let's create deployment descriptors. For client, web.xml is empty (using default JSP servlet) and for server we need a special servlet, that will handle requests for services and route them to corresponding service endpoint. So the files to be placed to webapp/WEB-INF folder are:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
    <listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>WSServlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WSServlet</servlet-name>
        <url-pattern>/my-service</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>60</session-timeout>
    </session-config>
</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">
    <endpoint
        name="MyDBService"
        implementation="org.mycompany.service.db.service.MyDBServiceImpl"
        url-pattern="/my-service"
    />
</endpoints>

And now we have to include jaxws-rt into our package, that actually holds the servlet and also tune maven-war-plugin to get WEB-INF resources from webapp folder:

pom.xml

<project>
    <groupId>org.mycompany.db-service</groupId>
    <artifactId>my-db-service</artifactId>
    <packaging>war</packaging>
    <version>0.1-SNAPSHOT</version>
    ...
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>my-db-common</artifactId>
            <version>0.1-SNAPSHOT</version>
        </dependency>
 
        <dependency>
            <groupId>com.sun.xml.ws</groupId>
            <artifactId>jaxws-rt</artifactId>
            <version>2.1.4</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
 
    <build>
        <sourceDirectory>src</sourceDirectory>
 
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <warSourceDirectory>webapp</warSourceDirectory>
                </configuration>
            </plugin>
            ...
        </plugins>
    </build>
</project>

Refer How to deploy to remote Tomcat concerning how to deploy our Web Service and client to remote Tomcat / JBoss.

Implementing a JAX-WS service using Spring JAX-WS

This approach is relatively simple to implement:

  • You have to mark your bean as @WebService as described above.
  • You add jaxws-spring dependency instead of jaxws-ri7):

    pom.xml

    <dependency>
        <groupId>org.jvnet.jax-ws-commons.spring</groupId>
        <artifactId>jaxws-spring</artifactId>
        <version>1.8</version>
        <scope>runtime</scope>
    </dependency>
  • You pass the location of your Spring Web context via servlet context attribute contextConfigLocation and use WSSpringServlet to handle requests.
  • Servlet URL and binding URL should be the same

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
    <!-- Context loader creates a Spring web contexts and puts it into servlet context. -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
 
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/application.context.xml</param-value>
    </context-param>
 
    <!-- This servlet lookups the Spring web context in servlet context and process the bindings. -->
    <servlet>
        <servlet-name>jaxws-servlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>jaxws-servlet</servlet-name>
        <url-pattern>/test-ws</url-pattern>
    </servlet-mapping>
</web-app>

WEB-INF/application.context.xml

<?xml version="1.0"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ws="http://jax-ws.dev.java.net/spring/core"
    xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://jax-ws.dev.java.net/spring/core https://jax-ws.dev.java.net/spring/core.xsd
        http://jax-ws.dev.java.net/spring/servlet https://jax-ws.dev.java.net/spring/servlet.xsd">
 
    <wss:binding url="/test-ws">
        <wss:service>
            <ws:service bean="#testWS"></ws:service>
        </wss:service>
    </wss:binding>
 
    <bean id="testWS" class="org.mycompany.service.db.service.MyDBServiceImpl">
        ...
    </bean>
</beans>

If you wish to use WSSpringServlet in more complicated configurations (e.g. when the service is wrapped into AOP proxy to support transactions) first head this note.

~~DISCUSSION~~

1) Also I do not recommend to do so, as java.util.Date does not carry any timezone information, that means, on the server side dates will always be presented as timestamps for GMT+0 timezone
2) See here and here for more information about JAXB javaType customization
3) More information about JAX-WS/JAXB customization you can find here and here
4) See here, here, here and here for more information about JAXB2 plugins
5) The sources of the plugin are available here
6) See here the note about how WSDL can be completely dynamically autogenerated
7) Due to issue#47 the dependant artefacts of jaxws-spring may not be correctly resolved
 
programming/java/web_service.txt · Последние изменения: 2010/08/19 14:08 — dmitry
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki