OMII-UK Home

Introduction to JAX-WS

Bryan Carpenter

1. Introduction

The Java API for XML-Based Web Services (JAX-WS) is an API proposed by Sun Microsystems as a replacement for their earlier JAX-RPC API. Version 2 of JAX-WS is Java Specification Request 224 (JSR 224).

Historically the OMII-UK Development Kit has used Apache Axis 1 for SOAP handling. Axis 1 provides an implementation of JAX-RPC, but not of JAX-WS. Current components in the OMII-UK toolkit typically use a mixture of JAX-RPC and proprietary Axis 1 APIs to generate and process SOAP.

More recent SOAP offerings from Apache―the competing Apache Axis2[1] and Apache CXF[2] projects―both implement JAX-WS. So far as I can tell, neither of them implements the older JAX-RPC API. The CXF User Guide recommends JAX-WS as a primary programming framework. Axis2 advocates its own APIs, and at the time of writing the JAX-WS interface was sparsely documented. But it does exist.

The JAX-WS Reference Implementation (JAX-WS-RI)[3] is developed within the GlassFish Community. It is another Open Source implementation of JAX-WS.

So these three leading candidates for “modern” Java SOAP implementations all implement JAX-WS, and none of them implements JAX-RPC. If the implementers are right, JAX-WS seems to be the way of the future.

This tutorial provides an introduction to the JAX-WS API. We compare the JAX-WS client API with JAX-RPC client API, and show how to achieve various effects using JAX-WS. For example we illustrate a way to use JAX-WS in conjunction with serialization frameworks like XMLBeans[4]. The examples in the tutorial all implement services and clients for simple “hello” services. There are several different client codes and a few different service codes. Each client example and each service example implements essentially the same functionality in a different way, illustrating a different aspect of JAX-WS. Any of the clients can be run against any of the services.

The source code for the examples given in this document is available as a Maven project. At the time of writing, the build of project can use Axis2, CXF, or JAX-WS-RI (through different Maven build “profiles”). The builds produce components that can be installed into the OMII-UK Web services container using the standard OMII-UK installers. Or it should be possible to run the example services in a unadorned servlet container like Tomcat.

Historically, an important plank of the SOAP platform provided in the OMII-UK Development Kit has been WS-Security. All three modern SOAP implementations referred to above provide WS-Security implementations, which can be used in conjunction with JAX-WS (two of them―Axis2 and JAX-WS-RI―use WS-SecurityPolicy as their WS-Security configuration language). But the current document focusses on the JAX-WS API itself―a later document may describe platform-specific security implementations.

1.1. Structure of this Tutorial

Section 2 of this tutorial describes the most basic example of a JAX-WS service and client, both based on a “Service Endpoint Interface” (SEI).

Section 3 introduces the suite of example codes that accompanies this tutorials, and explains how to build and run an example similar to the one described in Section 2.

Section 4 describes a number of different approaches to developing clients in JAX-WS, introducing the Dispatch interfaces. In particular Section 4.1 describes how to use the Dispatch<Source> interface, Section 4.2 describes how to use the Dispatch<SOAPMessage> interface in conjunction with the SAAJ API, and Section 4.3 describes how to use the Dispatch<Object> interface together with JAXB. Section 4.4 goes on to illustrate how JAX-WS can work with another popular serialization scheme―XMLBeans. Finally in this section on the client, Section 4.5 talks briefly about how asynchronous invocations work in JAX-WS.

Section 5 goes on to describe the Provider interface for developing services using JAX-WS without providing a SEI. Section 5.1 explains how to use the Provider<SOAPMessage> interface together with SAAJ to develop a services. Section 5.2 describes a way of using the Provider<Source> interface to develop a service based on XMLBeans.

Finally Section 6 talks about working with three different JAX-WS implementations. It illustrates how different build profiles can be used to target different implementations (Section 6.1). It briefly describes the configuration requirements of the implementations (Section 6.2). And it gives workarounds for some problems I encountered working with these implementations (Section 6.3).

2. A Simple Example

The JAX-WS API makes extensive use of the annotation mechanism introduced in Java 5. Example 1 illustrates use of several of these annotations in the definition of a very basic “hello” service.

Example 1. Simple JAX-WS Service

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

  @WebService(targetNamespace="http://test.omii.ac.uk")
  public class Hello {

      @WebMethod
      public String hello(@WebParam(name = "name") String name) {
          return "Hello " + name + "!";
      }
  } 

The annotated class Hello defines a Web service with just one operation called hello. That operation has one argument called name. The annotations in this class control the mapping of the Java code to a WSDL Web service[5].

The most important annotation in the example is the class annotation WebService, which―as we would expect―marks the class Hello as implementing a Web service. The WebService annotation can include various elements to configure non-default values of features like the service name, the port type name, and―as illustrated here―the target namespace of the WSDL.

The WebMethod annotation marks the method hello() as being exposed as a WSDL operation. In the present case this annotation is optional, because by default all public methods of Hello will be exposed in this way. But in general elements of the WebMethod annotation can control properties like the operation name in the WSDL, or can be used to exclude a method from the WSDL interface.

The WebParam method is used here to control the name of a parameter in the WSDL. Without this annotation the default parameter name would be arg0 rather than name.

When deployed as a JAX-WS Web service, the class of Example 1 will yield WSDL similar to that illustrated in Figure 1. Before deploying the class, it should be processed by a tool that generates any supporting artifacts associated with the Web service, like the JAXB classes that represent the schema types appearing in the WSDL. In the JAX-WS Reference Implementation, for example, this tool is called wsgen.

Figure 1. WSDL for simple Hello service

<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:tns="http://test.omii.ac.uk"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             targetNamespace="http://test.omii.ac.uk"
             name="HelloService">
  <types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
               targetNamespace="http://test.omii.ac.uk">
      <xs:element name="hello" type="tns:hello" /> 
      <xs:element name="helloResponse" type="tns:helloResponse" /> 
      <xs:complexType name="hello">
        <xs:sequence>
          <xs:element name="name" type="xs:string" minOccurs="0" /> 
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="helloResponse">
        <xs:sequence>
          <xs:element name="return" type="xs:string" minOccurs="0" /> 
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
  </types>
  <message name="hello">
    <part name="parameters" element="tns:hello" /> 
  </message>
  <message name="helloResponse">
    <part name="parameters" element="tns:helloResponse" /> 
  </message>
  <portType name="Hello">
    <operation name="hello">
      <input message="tns:hello" /> 
      <output message="tns:helloResponse" /> 
    </operation>
  </portType>
  <binding name="HelloPortBinding" type="tns:Hello">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
                  style="document" /> 
    <operation name="hello">
      <soap:operation soapAction="" /> 
      <input>
        <soap:body use="literal" /> 
      </input>
      <output>
        <soap:body use="literal" /> 
      </output>
    </operation>
  </binding>
  <service name="HelloService">
    <port name="HelloPort" binding="tns:HelloPortBinding">
      <soap:address location="http://localhost:8080/context/HelloService" /> 
    </port>
  </service>
</definitions> 

Notice how, in JAX-WS, annotations in the Java source take over some of the the customisations that in Axis 1 would require special entries in a Web Service Deployment Descriptor (WSDD) file.

A client for our Hello service can be as simple as Example 2. This example assumes that we used a WSDL-to-Java mapping tool to generate service classes and service endpoint interfaces (SEIs) from the WSDL.

Example 2. JAX-WS SOAP invocation using a generated SEI interface

  HelloService service = new HelloService();
  Hello port = service.getHelloPort();

  String response = port.hello(name); 

The instance of the service class HelloService corresponds to the service element from the WSDL. This class acts as a factory for proxy objects implementing the service endpoint interface Hello[6]. The Hello interface corresponds to the portType element in the WSDL, and the proxy that implements it corresponds to the port element.

In the JAX-WS Reference Implementation (for example) the class HelloService and the interface Hello are generated from WSDL by a tool called wsimport.

3. Running an Example

Download the file omii-jaxws-examples-src-dist-1.0.1.zip(info) and unpack it. Look in the directory omii-jaxws-examples-1.0.1/. You should find code similar to Example 1 in the subdirectory:

  server/sei-service/src/main/java/ 
and the client code similar to Example 2 under:
  client/sei-example/src/main/java/ 

To build the examples you should first install a recent version of Maven 2 (I used Maven 2.0.9).

Go into the directory omii-jaxws-examples-1.0.1/ and type:

  $ mvn install 
The first time you run this build it is likely to take several minutes because Maven will automatically download all dependencies. You must have an Internet connection available―at least the first time you run the build. Subsequent builds should run much faster.

Bear in mind that running Maven in this directory builds all examples in this report, so you should see many sub-projects being built.

By default the build uses the JAX-WS Reference Implementation for the client and for the server. We will see later how to build for different JAX-WS implementations. In any case you need to be using Java 5 or higher.

After a successful build you will find a WAR file for the service in[7]:

  server/sei-service-jaxws-ri-webapp/target/omii-sei-service-jaxws-ri-webapp-1.0.1.war 
Deploy this war file to a suitable servlet container―e.g. by copying it to the webapps/ directory of a Tomcat 5.5 installation.

Now visit a URL like:

  http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1/HelloService?wsdl 
with a Web browser. The exact form of the URL will depend on how your servlet container is set up. If everything has gone well you should see WSDL similar to Figure 1.

After a successful Maven build you should also find a client “distribution” in:

  client/dist/target/omii-jaxws-examples-client-dist-1.0.1.zip 
Unpack this zip file to a suitable location. Then, working in the omii-jaxws-examples-client-dist-1.0.1/ directory do something like:
  $ bin/run-example.sh example.SEIExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1 
Again the exact form of the URL will depend on the setup of your servlet container. Here run-example.sh is a script that runs the class example.SEIExample passing in the value of its second argument[8]. There is also a batch file run-example.bat that can be used on a Windows system.

Note these scripts use Apache Ant to set up class paths in a system independent way. To use them you must have Ant installed on your system, and the executable ant should available on your path.

If this runs successfully you should see output something like:

  Buildfile: bin/run-example.xml

  run:
       [java] Invoking simple service, using SEI...
       [java] Got: "Hello wonderful!"

  BUILD SUCCESSFUL 

The project structure of the example distribution may be intimidating at first glance. I made a deliberate choice to create many small subprojects, with each subproject containing a single example, illustrating a single feature. The hope is that you should be able to select a small handful of these subprojects, and take their code and configuration as templates, depending on your particular requirements.

In this section we have only used four subprojects in the directories:

  server/sei-service/
  server/sei-service-jaxws-ri-webapp/
  client/sei-example/
  client/dist/ 
The subproject in sei-service/ contains the Java code for the service class Hello. Maven runs the plugin version of wsgen, compiles the classes, and builds a jar. The subproject in sei-service-jaxws-ri-webapp has a dependency on this jar. Maven builds a WAR file containing the jar, and all transitive dependencies. The WAR also contains the configuration files you will find under:
  server/sei-service-jaxws-ri-webapp/src/main/webapp/ 

The subproject in sei-example/ contains the Java code for the client class SEIExample. Maven runs the plugin version of wsimport, reading the WSDL documents in the subdirectory:

  client/sei-example/src/wsdl/ 
Again it builds a jar. The subproject in dist/ has a dependency on this jar (and in this case on the client jars for other examples). This subproject builds a zip file containing these jar files, and and their transitive dependencies. The zip also contains the scripts you will find under:
  client/dist/src/dist-files/ 

The build of these also depends on the parent POMs:

  server/pom.xml
  client/pom.xml
  pom.xml 
but only to provide the definitions of a few global properties.

4. JAX-WS in the client

In the older JAX-RPC spec there were two ways to write clients. The first approach was to use a WSDL-to-Java mapping tool to generate service classes and service endpoint interfaces from WSDL. The generated Java interfaces reproduced the operations in the WSDL as Java methods. These methods could be called from the client application, and were transparently implemented as remote procedure calls. The second approach eschewed generating remote interfaces. Instead one used fixed Service and Call interfaces to explicitly invoke WSDL operations, without going through the WSDL-to-Java mapping stage. Documentation for JAX-RPC tended to emphasize the first approach, while many (most?) programmers tended to prefer the second.

JAX-WS is similar. The specification spends a lot of time describing how to invoke SOAP operations through generated classes and interfaces. We gave an example of this in Section 2. The fragment of JAX-WS client code in that section could equally well have been JAX-RPC.

Many programmers don't use JAX-RPC this way. Instead they often write code that looks more like Example 3.

Example 3. JAX-RPC SOAP invocation using the Call interface

  import javax.xml.rpc.Call;
  import javax.xml.rpc.Service;
  import javax.xml.rpc.ServiceFactory;
  [...]

  String endPoint = "http://localhost:8080/context/HelloService";

  Service service = ServiceFactory.newInstance().createService();

  Call call = service.createCall();

  call.setTargetEndpointAddress(endpoint);
  call.setOperationName(new QName(OPERATION_NAMESPACE, "hello"));

  String response = (String) call.invoke(new Object[] {name}); 

This form is obviously more long-winded, but it does have some advantages. It doesn't require availability of the WSDL at compile time (or even necessarily at run-time). Because it only needs run-time information about the service, the code can be more generic. And―at least in simple cases where we don't need to handle complex XML schema types―it avoids one stage in compilation (the WSDL-to-Java mapping).

That was JAX-RPC. JAX-WS allows comparable things, but the story is more complicated. The nearest equivalent to the Call interface in JAX-WS is the Dispatch interface. The Dispatch interface does not provide an RPC-like invoke() method that takes a list of arguments and returns a value. Instead the invoke() method of Dispatch requires the programmer to explicitly assemble a representation of the request message, and it returns a representation of the response message[9].

There are several ways of representing the messages. Dispatch is a parameterized interface. Implemented variants include Dispatch<Source>, Dispatch<SOAPMessage>, and Dispatch<Object>.

The first version of Dispatch uses javax.xml.transform.Source objects to represent messages. These can wrap conventional DOM trees or XML source files. Use of the javax.xml.transform package makes this form convenient if you plan to use XSLT to extract data from the response message. But―that aside― this package also provides a convenient API for simply copying DOMs. This can be handy, as we illustrate in a couple of examples below.

The second version of Dispatch uses javax.xml.soap.SOAPMessage objects to represent messages. This makes the SOAP with Attachments API for Java (SAAJ) available for building request messages and analysing response messages.

The third version nominally uses java.lang.Object objects to represent messages. What this actually means is that JAXB[10] is used to marshal an object into an XML request message, and to unmarshal response messages.

Other versions of Dispatch may be provided by an implementation of JAX-WS (and one more, using javax.activation.DataSource, must be provided, but this is not designed to be used with SOAP).

4.1. Using the Dispatch<Source> Interface

Example 4 illustrates the Dispatch<Source> variant.

Example 4. JAX-WS SOAP invocation using the Dispatch<Source> interface

  import javax.xml.namespace.QName;

  import javax.xml.ws.Service;
  import javax.xml.ws.Dispatch;

  import javax.xml.transform.Source;
  import javax.xml.transform.stream.StreamSource;
  import javax.xml.transform.stream.StreamResult;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.Transformer;
  [...]

  QName serviceQName =
          new QName("http://test.omii.ac.uk", "HelloService");
  QName portQName = new QName("http://test.omii.ac.uk", "HelloPort"); 

  Service service = Service.create(wsdlLocation, serviceQName); 

  Dispatch<Source> dispatch =
          service.createDispatch(portQName, Source.class,
                                 Service.Mode.PAYLOAD);

  Source request = new StreamSource(requestStream);

  Source response = dispatch.invoke(request);

  Transformer copier = TransformerFactory.newInstance().newTransformer();

  copier.transform(response, new StreamResult(System.out)); 

It is noticeable that, compared with Example 3, the code in Example 4 has more dependence on the detailed features of the service WSDL. We need to know the service name (HelloService) the port name (HelloPort) and the WSDL target namespace (http://test.omii.ac.uk), not to mention the URL location of the WSDL itself. The code in Example 4 first creates a Service object, passing it the location of the service WSDL[11].

The Service object acts as a factory to create a Dispatch<Source> object. In general when you create a Dispatch object you can specify the mode as MESSAGE or PAYLOAD. In the MESSAGE case the programmer is responsible for building or processing complete messages including the SOAP envelope. In the PAYLOAD case the argument and result of the invoke() method, for example, just represents the XML inside the SOAP Body element.

The example assumes that requestStream is an InputStream that provides the XML source of request payload[12], which in our case might be:

  <ns1:hello xmlns:ns1="http://test.omii.ac.uk">
      <name>wonderful</name>
  </ns1:hello> 
A StreamSource object wrapping this XML fragment is constructed, and passed as the argument to the invoke() method. The result is a new Source object wrapping the response payload. In this simplified example we just copy this payload to System.out, using APIs from the javax.xml.transform package. It prints out something like:
  <ns2:helloResponse xmlns:ns2="http://test.omii.ac.uk">
    <return>Hello wonderful!</return>
  </ns2:helloResponse> 

The accompanying example suite includes a more complete version of Example 4 in the subdirectory:

  client/dispatch-source-example/src/main/java/ 
You can run this example by following the instructions in Section 3 then running a command like:
  $ bin/run-example.sh example.DispatchSourceExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1 
in the client distribution directory. If this is successful, you should see output similar to:
  Buildfile: bin/run-example.xml

  run:
       [java] Invoking simple service, using Dispatch<Source>...
       [java] Got: "<?xml version="1.0" encoding="UTF-8"?><ns2:helloResponse xmlns:ns2="http://test.omii.ac.uk" xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><return>Hello wonderful!</return></ns2:helloResponse>"

  BUILD SUCCESSFUL 

Later in Example 7 we give a more realistic example using Dispatch<Source>.

4.2. Using the Dispatch<SOAPMessage> Interface

Example 5. illustrates the SOAPMessage variant of Dispatch. This is a bit more complex, largely because we have to explicitly construct the request message using the SAAJ API. Creation of the service is identical to Example 4 and we omit the details. A Dispatch instance is created, this time passing SOAPMessage.class to the creation method. Here we specify MESSAGE mode[13]

Example 5. JAX-WS invocation using the Dispatch<SOAPMessage> interface

  import javax.xml.soap.MessageFactory;
  import javax.xml.soap.SOAPMessage;
  import javax.xml.soap.SOAPEnvelope;
  [...]

  [... create `service' ...]

  Dispatch<SOAPMessage> dispatch =
          service.createDispatch(portQName, SOAPMessage.class,
                                 Service.Mode.MESSAGE);

  MessageFactory factory = MessageFactory.newInstance();

  SOAPMessage request = factory.createMessage();
  SOAPEnvelope envelope = request.getSOAPPart().getEnvelope();

  request.getSOAPBody() .
          addBodyElement(envelope.createName("hello", "ns1",    
                                             "http://test.omii.ac.uk")) .
          addChildElement(envelope.createName("name")) .
          addTextNode(name) ;

  SOAPMessage response = dispatch.invoke(request); 

The next several lines of the code in Example 5 are concerned with constructing the request SOAP message. This code is using SAAJ to build a message whose body contains:

  <ns1:hello xmlns:ns1="http://test.omii.ac.uk">
      <name>''name''</name>
  </ns1:hello> 
Finally this message is passed to the invoke() method of Dispatch, which returns another SOAPMessage object representing the response. We can again using the SAAJ API to extract data from this response.

The example suite includes a version of Example 5 in the subdirectory:

  client/dispatch-soapmessage-example/src/main/java/ 
You can run this example by a command like:
  $ bin/run-example.sh example.DispatchSOAPMessageExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1 
in the client distribution directory. The output should be similar to the example in Section 3.

4.3. Using the Dispatch<Object> Interface

Example 6 shows an equivalent thing using a Dispatch<Object>. Construction of the service instance is again identical. But now we use a different overloaded version of createDispatch() that takes an instance of JAXBContext as its second argument, instead of an instance of Class<T>.

Example 6. JAX-WS invocation using the Dispatch<Object> interface with JAXB

  import javax.xml.bind.JAXBContext;
  import javax.xml.bind.JAXBElement;
  [...]

  [... create `service' ...]

  JAXBContext jc = JAXBContext.newInstance("example.jaxws") ;

  Dispatch<Object> dispatch =
          service.createDispatch(portQName, jc, Service.Mode.PAYLOAD);

  ObjectFactory of = new ObjectFactory() ;

  Hello_Type requestValue = of.createHello_Type();
  requestValue.setName(name);
  JAXBElement<Hello_Type> request = of.createHello(requestValue);

  JAXBElement<HelloResponse> response =
          (JAXBElement<HelloResponse>) dispatch.invoke(request) ;

  String result = response.getValue().getReturn(); 

In this example the classes ObjectFactory, Hello_Type, and HelloResponse are generated classes, created from the WSDL schema by a tool like wsimport. We assume here that they have been created in the package example.jaxws. In JAXB 2, instances of generated classes like Hello_Type stand for instantiations of a schema complex type (which is somehow the value of an element), not instances of elements themselves. These objects have to be wrapped in JAXBElement instances to represent XML elements. This adds a little bit of extra complexity to Example 6. Once the representation of the hello element is constructed, it is passed to invoke() in the now-familiar way. Data can easily be extracted from the returned element JAXBElement<HelloResponse> element using JAXB accessors.

The example suite includes a version of Example 6 in the subdirectory:

  client/dispatch-object-example/src/main/java/ 
You can run this example from the client installation directory by a command like:
  $ bin/run-example.sh example.DispatchObjectExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1 
in the client distribution directory. Again the output should be similar to the example in Section 3.

4.4. Using Another Serialization Framework

JAXB is the only serialization framework directly supported by JAX-WS, and there doesn't appear to be any intention of generalizing JAX-WS in this respect. This is likely to be an issue when porting existing applications that use different frameworks to use JAX-WS. Classes generated by an application's favoured serialization infrastructure are often used pervasively throughout the application code―not just at the point of SOAP invocation. So changing this framework may involve global changes to code.

One solution is to use a Dispatch<Source> instance to make invocations. Example 7 gives highly simplified example of an application that is using Apache XMLBeans.

Example 7. JAX-WS invocation using Dispatch<Source> with XMLBeans

  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.dom.DOMResult;
  [...]

  [... create `service' ...]

  Dispatch<Source> dispatch =
          service.createDispatch(portQName, Source.class,
                                 Service.Mode.PAYLOAD);

  Transformer copier =
          TransformerFactory.newInstance().newTransformer();

  HelloDocument requestDoc = [...];

  Source request = new DOMSource(requestDoc.getDomNode());

  Source response = dispatch.invoke(request);

  DOMResult responseDOM = new DOMResult();
  copier.transform(response, responseDOM);

  HelloResponseDocument responseDoc =
          HelloResponseDocument.Factory.parse(responseDOM.getNode()); 

Here we assume that the classes HelloDocument and HelloResponseDocument are classes generated by the XMLBeans framework―subclasses of org.apache.xmlbeans.XmlObject. The example omitted the creation of the HelloDocument instance, because we are assuming it exists in some pre-existing application. But it could be created by code like this:

  HelloDocument requestDoc = HelloDocument.Factory.newInstance();
  requestDoc.addNewHello().setName(name); 
In any case, the DOM of this object is extracted using the getDomNode() method of XmlObject, and wrapped in a DOMSource object which can be passed to the invoke() method.

The returned Source object then (perhaps unfortunately) has to be copied to a DOMResult object before its DOM can be extracted and parsed into an XMLBeans HelloResponseDocument. With the response data encapsulated in XMLBeans format our “legacy” application can take over the processing.

The example suite includes a code similar to Example 7 in the subdirectory:

  client/xmlbeans-example/src/main/java/ 
You can run this example from the client installation directory by a command like:
  $ bin/run-example.sh example.XMLBeansExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1 
in the client distribution directory. Again the output should be similar to the example in Section 3.

The version in the example suite differs slightly from Example 7 for reasons discussed in Section 6.

4.5. Asynchronous Invocations

All the examples given so far have been “synchronous” invocations. The call to the proxy SEI method, or to the invoke() method of Dispatch, does not complete until a response message has arrived from the service.

JAX-WS provides two ways of performing invocation asynchronously. Both mechanisms require using the Dispatch interface.

One mechanisms uses “callbacks”. The programmer defines a subclass of AsyncHandler implementing a handleResponse(). An instance of this class is passed as an extra argument to the invokeAsync() method of Dispatch.

The other mechanism uses “polling”. There is a variant of invokeAsync() that doesn't take a handler argument. It rerturns a Response object. This can be polled periodically with an isDone() method until the response message arrives.

Complete examples are available in the example suite in the directories:

  client/asynchronous-callback-example/src/main/java/example/
  client/asynchronous-polling-example/src/main/java/example/ 
Run them by commands like:
  $ bin/run-example.sh example.AsynchronousCallbackExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1
  $ bin/run-example.sh example.AsynchronousPollingExample http://localhost:8080/omii-sei-service-jaxws-ri-webapp-1.0.1
 
respectively.

5. More Advanced Programming on the Server Side

Having dedicated a lot of space to describing the client side Dispatch<T> interface, we should also say something about its analogue on the server side―the Provider<T> interface.

5.1. The Provider<SOAPMessage> Interface

The Hello class in Example 1 was a service endpoint implementation class, annotated as a WebService. The JAX-WS specification refers to these kinds of services as “SEI-based endpoints”, though in Example 1 the Service Endpoint Interface is only implicitly defined by the implementation class.

An alternative way to define a service is by implementing the Provider interface, and annotating the class as a WebServiceProvider. This is illustrated in Example 8.

Example 8. JAX-WS Service using Provider<SOAPMessage>

  import javax.xml.ws.WebServiceProvider;
  import javax.xml.ws.ServiceMode;
  
  import javax.xml.ws.Provider;
  import javax.xml.ws.Service;
  
  import javax.xml.soap.MessageFactory;
  import javax.xml.soap.SOAPMessage;
  import javax.xml.soap.SOAPEnvelope;
  import javax.xml.soap.SOAPException;
  
  import org.w3c.dom.Element ;
  
  @WebServiceProvider(targetNamespace="http://test.omii.ac.uk",
  	      serviceName="HelloService",
  	      portName="HelloPort")
  @ServiceMode(value=Service.Mode.MESSAGE)
  public class MyService implements Provider<SOAPMessage> {
  
  public SOAPMessage invoke(SOAPMessage request) {
  
    try {
        String name = request.getSOAPBody() .
  		    getElementsByTagName("name").item(0) .
  		    getFirstChild().getNodeValue();
  
        MessageFactory factory = MessageFactory.newInstance();
  
        SOAPMessage response = factory.createMessage();
        SOAPEnvelope envelope = response.getSOAPPart().getEnvelope();
  
        response.getSOAPBody() .
  	      addBodyElement(envelope .
  		      createName("helloResponse",
  				 "ns1", "http://test.omii.ac.uk")) .
  	      addChildElement(envelope.createName("return")) .
  	      addTextNode("Hello " + name + "!");
  
        return response;
    }
    catch(SOAPException e) {
        throw new RuntimeException(e);
    }
  }
} 

Example 8 should be compared with Example 5. The invoke() method here will be called for any incoming SOAP message. In the case of a Provider<SOAPMessage> implementation the incoming message is analysed―and the response message is constructed―using the SAAJ API.

In itself Example 8 is not enough to define a WSDL Web service. In contrast to the Hello class in Example 2, the MyService class by itself does not contain enough detailed information for this. A Provider-based implementation can (and I think essentially must) define the service name and port name in the WebServiceProvider annotation. But we are still missing metadata on the supported set of operations. Our intention―as in other examples in this tutorial―is that the service should support a hello operation taking a name parameter. But there is no mechanical way of telling this from the implementation of the class MyService.

Instead, the structure of the service WSDL must be externally defined for a Provider-based service. This is done by explicitly providing a prototype WSDL document. It is possible to define the location of this documentation in the Java implementation class, by defining the wsdlLocation element of the WebServiceProvider annotation. The value of this annotation should be a relative URL. But the exact interpretation of the path in the URL is somewhat implementation dependent, so it is omitted in Example 8. Instead we assume that wsdlLocation is defined in some implementation-dependent deployment descriptor―for example in the JAX-WS Reference Implementation this could be defined in the file called sun-jaxws.xml. The WSDL in Figure 1 would serve our purposes[14].

The code of Example 8 exploits the knowledge that only a single operation is supported. More generally a pattern something like this might be used[15]:

  Element wrapper =
          (Element) request.getSOAPBody().getElementsByTagName("*").item(0);
  String op = wrapper.getLocalName() ;
  if("''operation1''".equals(op))) {
    ''[... extract arguments of operation1 from'' wrapper ''...]''
  } else if("''operation2''".equals(op))) {
    ''[... extract arguments of operation2 from'' wrapper ''...]''
  } [...] 
In the example we threw a general Java RuntimeException to report an error. To control the SOAP fault in more detail one can throw a javax.xml.ws.soap.SOAPFaultException.

For a service defined using the Provider interface, there is no need to use the wsgen tool.

The accompanying example suite includes a version of Example 8 in the subdirectory:

  server/provider-soapmessage-service/src/main/java/ 
You can deploy this example by following the build instructions in Section 3 The subproject server/provider-soapmessage-service-jaxws-ri-webapp/ generates a WAR archive containing jars and configuration files for the JAX-WS reference implementation[16].

After a successful build you should find the generated WAR at:

  server/provider-soapmessage-service-jaxws-ri-webapp/target/omii-provider-soapmessage-service-jaxws-ri-webapp-1.0.1.war 
Deploy this war file to a suitable servlet container. Then, in a client distribution directory, run a command such as:
  $ bin/run-example.sh example.SEIExample http://localhost:8080/omii-provider-soapmessage-service-jaxws-ri-webapp-1.0.1 

You can also run any of the clients in Section 4 against this service. Follow the same instructions as before―just remember to change the webapp name in the URL.

5.2. The Provider<Source> Interface, and XMLBeans in the Service

Besides Provider<SOAPMessage> in MESSAGE mode, every JAX-WS implementation must support defining services using Provider<Source> in PAYLOAD mode. By analogy with Dispatch<Object>, you might also expect Provider<Object> to support Provider-based service implementation using JAXB explicitly, but the specification does not require this. To close this section, we illustrate how to define a service using another serialization framework―XMLBeans. The code is sketched in Example 9.

Example 9. JAX-WS Service using Provider<Source> with XMLBeans

  import javax.xml.ws.WebServiceProvider;
  import javax.xml.ws.ServiceMode;
  
  import javax.xml.ws.Provider;
  import javax.xml.ws.Service;
  
  [...]
  import javax.xml.transform.TransformerException;
  
  @WebServiceProvider(targetNamespace="http://test.omii.ac.uk",
  	      serviceName="HelloService",
  	      portName="HelloPort")
  @ServiceMode(value=Service.Mode.PAYLOAD)
  public class MyService implements Provider<Source> {
  
  public Source invoke(Source request) {
  
    try {
        Transformer copier =
  	      TransformerFactory.newInstance().newTransformer();
  
        DOMResult requestDOM = new DOMResult();
        copier.transform(request, requestDOM);
  
        HelloDocument requestDoc =
  	      HelloDocument.Factory.parse(requestDOM.getNode());
  
        [... process requestDoc and generate responseDoc ...]
  
        Source response = new DOMSource(responseDoc.getDomNode());
  
        return response;
    }
    catch(TransformerException e) {
        throw new RuntimeException(e);
    }
    [...]
  }
}
 

The details are very similar to Example 7. In the example we assume that this service wraps some legacy XMLBeans service that processes the bean requestDoc to produce the bean responseDoc. But as a simple example the ellided code might look like this:

  String name = requestDoc.getHello().getName();

  HelloResponseDocument responseDoc =
          HelloResponseDocument.Factory.newInstance();
  responseDoc.addNewHelloResponse().setReturn("Hello " + name + "!"); 
As with Example 8, it is necessary to provide metadata on the supported operations in the form of an explicit WSDL document.

The example suite includes a version of Example 9 in the subdirectory:

  server/xmlbeans-service/src/main/java/ 
After a successful build you should find the associated WAR at:
  server/xmlbeans-service-jaxws-ri-webapp/target/omii-xmlbeans-service-jaxws-ri-webapp-1.0.1.war 
Deploy this war file to a suitable servlet container. Then run a command like:
  $ bin/run-example.sh example.SEIExample http://localhost:8080/omii-xmlbeans-service-jaxws-ri-webapp-1.0.1 
to exercise your new service.

6. Working with JAX-WS Implementations

6.1. Build Profiles for the Example Suite

The default build of the example suite accompanying this tutorial uses the JAX-WS Reference Implementation. As indicated earlier, the example suite can also be built to use Apache Axis2 or Apache CXF runtime environments. The main differences are in the set of jar files included in generated client and service distributions, and the details of the configuration files in the example services.

The Maven build of the example suite supports three profiles: jaxws-ri, axis2, and cxf. The default profile is jaxws-ri.

Use the axis2 profile as follows[17]:

  $ mvn -Paxis2 install 
A different set of WAR distributions for the example services will be built. These are associated with the subprojects:
  server/sei-service-axis2-webapp/
  server/provider-soapmessage-service-axis2-webapp/
  server/xmlbeans-service-axis2-webapp/
 
You will find the generated WAR files in the target/ subdirectories of these individual subprojects, after a successful build.

A typical command to run a client against one of these services might look like this:

  $ bin/run-example.sh example.SEIExample http://localhost:8080/omii-sei-service-axis2-webapp-1.0.1/services 
Note the extra path component services/ in the URL[18].

Likewise the cxf profile is used as follows:

  $ mvn -Pcxf install 
and the set of WAR distributions are associated with the subprojects:
  server/sei-service-cxf-webapp/
  server/provider-soapmessage-service-cxf-webapp/
  server/xmlbeans-service-cxf-webapp/
 
You will find the generated WAR files in the target/ subdirectories of these individual subprojects, after a successful build.

Regardless of profile, the client distribution is built by the subproject client/dist/. But the set of jars contained within this distribution will differ, depending on the build profile.

A typical command to run a client against one of these services might be:

  $ bin/run-example.sh example.SEIExample http://localhost:8080/omii-sei-service-cxf-webapp-1.0.1/services 
Again note the path component services/ in the URL.

6.2. Configuration Files

The default configuration file for a web application based on the JAX-WS Reference Implementation is:

  WEB-INF/sun-jaxws.xml 
A typical sun-jaxws.xml file might look something like:
  <endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
    <endpoint name='omii-jaxws-example'
              implementation='example.Hello'
              url-pattern='/HelloService'/>
  </endpoints> 
For services that need “original WSDL”, the <endpoint> element should have a wsdlLocation element, e.g.:
  <endpoint name='omii-jaxws-example'
            implementation='example.MyService'
            wsdlLocation='WEB-INF/wsdl/HelloService.wsdl'
            url-pattern='/HelloService'/> 
In this example, clearly, the WSDL and referenced XML schema go in the WEB-INF/wsdl/ directory.

An Axis2 web application has a central configuration file in:

  WEB-INF/conf/axis2.xml 
but in simple cases you can get by with the version of this file distributed in axis2.war. Per-service configuration is done in a file like:
  WEB-INF/services/sei-service/META-INF/services.xml 
The exact name of the subdirectory called sei-service/ here doesn't seem to be particularly significant[19]. The services.xml file might contain something like:
  <serviceGroup>
    <service name="HelloService" scope="application">

      <description>Simple Hello Service</description>

      <messageReceivers>
        <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
            class="org.apache.axis2.jaxws.server.JAXWSMessageReceiver"/>
      </messageReceivers>
      <parameter name="ServiceClass">example.Hello</parameter>
      <parameter name="useOriginalwsdl">true</parameter>
    </service>
  </serviceGroup> 
By default “original WSDL” will be picked up from the same directory as the services.xml file.

Our example builds on Apache CXF are configured using Spring, in which case the configuration file may be placed on the class path, e.g. at:

  WEB-INF/classes/services.xml 
Example contents of this file might be:
  <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jaxws="http://cxf.apache.org/jaxws"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
  
    <import resource="classpath:META-INF/cxf/cxf.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
  
    <jaxws:endpoint id="omii-jaxws-example"
                    implementor="example.Hello"
                    address="/HelloService"/>
  </beans> 
For services that need “original WSDL”, the <endpoint> element should have a wsdlLocation element, e.g.:
  <jaxws:endpoint id="omii-jaxws-example"
                  implementor="example.Hello"
                  address="/HelloService"
                  wsdlLocation="classpath:wsdl/HelloService.wsdl"/> 
In this case WSDL files could go in the WEB-INF/classes/wsdl/ directory.

In all three cases you will have to define servlets, servlet mappings, and possibly listeners, in WEB-INF/web.xml. In the case of CXF, a context parameter defined in web.xml specifies the location of the configuration file called services.xml above.

6.3. Workarounds

That's how I think it should all work. But at the time of writing there are a few glitches you have to work around. This section describes the ones I encountered while putting together my example suite. The first two problems are associated with the XMLBeans examples.

In Example 7 and Example 9 I had problems because most recent version of the JAX-WS Reference Implementation was trying to take advantage of level 3 DOM features in the request document DOM, and the most recent version of XMLBeans didn't implement those features. A possible workaround is to add one more copying operation to create a fresh DOM―in Example 7, for example, replacing the line:

  Source request = new DOMSource(requestDoc.getDomNode()); 
with the lines:
  DOMResult requestCopy = new DOMResult();
  copier.transform(new DOMSource(requestDoc.getDomNode()), requestCopy);
  Source request = new DOMSource(requestCopy.getNode()); 

But a different problem afflicts Axis2 when used in conjunction with XMLBeans. The DOM created by XMLBeans (version 2.1.0) has null for the namespaceURI elements that have no namespace. I believe this is “correct”, but Axis2 is expecting a namespaceURI value of "" to represent this. A workaround for this is to just serialize the XMLBeans DOM to text, and let the Axis2 runtime do what it likes with the text representation. So:

  Source request = new DOMSource(requestDoc.getDomNode()); 
is replaced with:
  Source request =
          new javax.xml.transform.stream.StreamSource(
                  new java.io.ByteArrayInputStream(requestDoc.toString() .
                                                   getBytes())); 
This presumably introduces a significant overhead, but it avoids all issues of compatibility between DOMS.

Example 8 as presented above doesn't work with the version of Axis2 I was using. This isn't due to JAX-WS features, but there is a bug in the implementation of getElementsByTagName() in Axiom 1.2.5 (the Axis2 object tree). The example suite contains a workaround for this problem.

There is a more fundamental issue in writing SEI-based JAX-WS services using Axis2. At the time of writing the WSDL generated by the Axis2 runtime does not reflect the contents of the JAX-WS annotations[20]. This means that in practise you normally need to provide your own WSDL for JAX-WS, even in the SEI-based case. You can't rely on the WSDL generated by the runtime. This is specified by setting the useOriginalwsdl flag in services.xml.


[#1]Axis2 Home page: http://ws.apache.org/axis2.

[#2]Apache CXF Home page: http://cxf.apache.org.

[#3]JAX-WS Reference Implementation: https://jax-ws.dev.java.net.

[#4]Apache XMLBeans is the serialization framework used by GridSAM, for example.

[#5] Strictly speaking, the annotations WebService, WebMethod, and WebParam aren't part of the JAX-WS specification itself, but a separate specification called Web Services Metadata for the Java Platform (JSR 181).

[#6]The generated interface Hello in the client is distinct for our implementation class Hello on the client, though they will have similar or identical public methods

[#7]The JAX-WS implementation-independent source code for the service is in the Maven subproject server/sei-service/. The subproject server/sei-service-jaxws-ri-webapp/ generates a WAR archive containing jars and configuration files specific to the JAX-WS reference implementation.

[#8]This argument is actually passed in as the value of the system property webapp.url, which is in turn read by SEIExample and other client examples.

[#9]To this extent, it is similar to the Axiom interface to the Axis2 client.

[#10]JavaTM Architecture for XML Binding (JAXB) 2.0: http://jcp.org/en/jsr/detail?id=222.

[#11]It is possible to create a Service object without passing it the WSDL. Instead you can manually add a named port and associated endpoint URL to the Service object using the addPort() method. But we failed to get this approach to work in conjunction with the WSIT implementation of WS-Security; providing the WSDL seems to be the safest approach.

[#12]The stream might be a ByteArrayInputStream loading the XML from a byte array or String constructed by the program.

[#13]It's hard to see how PAYLOAD makes sense for Dispatch<SOAPMessage>, because the type of the invoke() argument and results would have to be SOAPElement rather than SOAPMessage.

[#14]A problem with providing the WSDL explicitly in the service definition is that a primary WSDL document contains a fixed endpoint address. This endpoint address typically isn't known at “compile-time” for the service. The JAX-WS specification requires that: “At publishing time, a JAX-WS implementation MUST patch the endpoint address in the root description document to match the actual address the endpoint is deployed at.” So the address http://localhost:8080/context/HelloService in Figure 1 would be replaced at runtime by the actual address at which the service is deployed.

[#15]In a production Web service one would probably want to use a more efficient method than getElementsByTagName(*) to get the wrapper. This method finds all descendent elements in preorder. So, applied to the SOAP body, the first element in the returned list will be the immediately nested operation-wrapper element (naively using getFirstChild() doesn't work here because it could return an insignificant TEXT node).

[#16]For this particular implementation, the WSDL for the service should be placed in a location defined in WEB-INF/sun-jaxws.xml.

[#17]Before you do this you should have run the command:

  $ mvn install:install-file -DgroupId=org.apache.axis2 \
                             -DartifactId=axis2-webapp -Dversion=1.3 \
                             -Dpackaging=war -Dfile=axis2.war 
to load the prototype Axis2 WAR into your local Maven repository.

[#18]I assume it is possible to change this by altering the servlet mapping in the web.xml file, but I didn't try this.

[#19]In general the META-INF/ directory can be embedded in an .aar archive, but for the sake of simplicity our examples use “exploded” service directories

[#20]See http://issues.apache.org/jira/browse/AXIS2-2347.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
zip
omii-jaxws-examples-src-dist-1... 16942.0 kB 1 17-Dec-2008 15:31 BryanCarpenter update software distribution
« This page (revision-37) was last changed on 20-Nov-2009 15:18 by SimonHettrick [RSS]

© The University of Southampton on behalf of OMII-UK. All Rights Reserved. | Terms of Use | Privacy Policy | PageRank Checker