Chapter 6.  Create a web service client for a SOAP based web service

6.1.  Create a standalone client.

6.1.1.  Use wsimport to generate artifacts.

JAX-WS defines two service usage models:

  • Proxy clients.

  • Dispatch clients.

In proxy-based client model model, your applications work on local proxy objects that implement the SEI that is being exposed by the web service endpoint.

The dispatch-client model offered by JAX-WS is a lower-level model that requires you to supply the necessary XML request yourself. This model can be used in the situations where you want to dynamically build up the SOAP request itself or where you must use a non-SOAP-based web service endpoint.

Collectively, both client types are also known as BindingProviders because both clients realize the JAX-WS javax.xml.ws.BindingProvider interface. The BindingProvider interface allows for a common configuration model.

Proxy clients

The wsimport tool will read the WSDL of a deployed web service and generate the Java objects necessary to invoke it, including a class that extends javax.xml.ws.Service, which provides the client view of a web service. This can be a confusing concept because we tend to think of the service as being located on the server. But a service instance acts as a factory to create proxies that allow you to invoke a web service as if it was local. These proxies are sometimes referred to as SEI (Service Endpoint Interface) objects.

The tool generates portable artifacts that use only standard Java means. It will automatically call on JAXB to create value types that map Java to XML tand the result can be used to perform web services operations.

Let's use a simple calculator web service as an example. The WSDL of your service is located at http://localhost:4933/CalculatorApp/CalculatorWSService?wsdl. It defines a single port type, as in the following code:


<portType name="CalculatorWS">
    <operation name="add">
        <input message="tns:add"></input>
        <output message="tns:addResponse"></output>
    </operation>
</portType>

//The service name element is:
<service name="CalculatorWSService">

						

Now run the wsimport tool to create a proxy so you can invoke the service. Here is the command and the output:


>wsimport  -d /home/mz -target 2.1 -verbose http://localhost:4933/CalculatorApp/CalculatorWSService?wsdl
parsing WSDL...

generating code...
org\me\calculator\Add.java
org\me\calculator\AddResponse.java
org\me\calculator\CalculatorWS.java
org\me\calculator\CalculatorWSService.java
org\me\calculator\ObjectFactory.java
org\me\calculator\package-info.java

compiling code...
javac -d /home/mz -classpath ...
>

						

The wsimport tool has a variety of options, many of which have to do with customization. But in the basic invocation, you pass the tool the options you want and the final argument is the location of the WSDL. The first option, -d, indicates the directory where you want the imported source code to be written. The -target option is used to specify the version of JAX-WS you want to be compatible with (2.0 is the default), and the -verbose option tells the tool to indicate the work it is doing as it does it.

If you want to have wsimport retain the Java source files it generates in addition to the *.class files, use the -keep option.

The generated Service class

In this example, the Service class is called CalculatorWSService.java, which corresponds to the value of the name attribute of the WSDL <service> element. The generated Service class allows you to:

  • Get available ports (service endpoint interfaces).

  • Get the location of the WSDL document associated with the service.

  • Get the Executor instance associated with the service, which provides threading capability to service invocations.

  • Create a Dispatch.

  • Create a Service instance.

  • Call the getPort method on the Service instance to invoke web service operations.

This class extends javax.xml.ws.Service, and is annotated with a @WebServiceClient annotation that specifies the location of the WSDL representing the service to be invoked. It contains factory methods that return the Java object that represents the WSDL port you can invoke operations on. The generated Service class looks like this:

@WebServiceClient(name = "CalculatorWSService",
    targetNamespace = "http://calculator.me.org/",
    wsdlLocation = "http://localhost:4933/CalculatorApp/CalculatorWSService?wsdl")
public class CalculatorWSService extends Service {

    //...

    @WebEndpoint(name = "CalculatorWSPort")
    public CalculatorWS getCalculatorWSPort() {
        return super.getPort(new QName("http://calculator.me.org/", "CalculatorWSPort"), CalculatorWS.class);
    }

    //...
}
						

Here the getCalculatorWSPort method returns an object that implements the CalculatorWS interface, which is discussed next. The no-arg getPort method can be used in general; the second getPort method accepts a variable-length array of javax.xml.ws.WebServiceFeature objects that can be used by clients to configure certain aspects of the invocation, such as whether to enable MTOM or WS-Addressing.

The generated Port class

Because the WSDL port as shown in the earlier listing has a value of CalculatorWS for the name attribute, that is the name of the generated Java interface representing the port. This interface is annotated with @WebService, to indicate that it is a service endpoint interface that will be used as a proxy. There is not an implementation for this class generated by JAX-WS. The runtime will do the work behind the scenes by delegating the invocation to an implementation of javax.xml.ws.spi.ServiceDelegate, which the Service class decorates.

In the calculator example, the port has one method, to match the single add operation defined in the WSDL:

@WebMethod
@WebResult(targetNamespace = "")
@RequestWrapper(localName = "add",
	targetNamespace = "http://calculator.me.org/",
	className = "org.me.calculator.Add")
@ResponseWrapper(localName = "addResponse",
	targetNamespace = "http://calculator.me.org/",
	className = "org.me.calculator.AddResponse")
public int add(
    @WebParam(name = "i", targetNamespace = "")
    int i,
    @WebParam(name = "j", targetNamespace = "")
    int j
);
						

As you can see, the add method has a variety of annotations.

First, your WSDL specifies the following in the messages section:


<message name="add">
    <part name="parameters" element="tns:add"></part>
</message>

						

So the SEI needs to account for this message, and creates an annotation indicating that the runtime will create a message with a QName that contains a local part of add, in the specified namespace. That message is derived from the Java class that is also generated, org.me.calculator.Add, which looks like this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "add", propOrder = {
    "i",
    "j"
})
public class Add {

    protected int i;
    protected int j;

    // Getters and setters omitted
}
						

That class acts as the wrapper for each of the integers that will be sent in the request. The @XmlXXXXX annotations on this class come from JAXB. They indicate how JAXB should marshal and unmarshal instances of this class to and from XML. The @XmlType annotation is used to specify that this Add class matches a top-level complex type (or an enum) within an XML schema, and the "name" property is specified as "add" in it, to match the item's name within the schema. If you look at the schema that your WSDL refers to, you see the following complex type, which matches your Add class:


<xs:complexType name="add">
    <xs:sequence>
        <xs:element name="i" type="xs:int"></xs:element>
        <xs:element name="j" type="xs:int"></xs:element>
    </xs:sequence>
</xs:complexType>

						

Integers are defined as basic types provided with XML Schema; they are not custom types that you have written that require something special. The complex type that wraps these two integers is created in order to match your WSDL, which uses the document/literal style. Here is the portion of the WSDL that tells you this:


<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"></soap:binding>

<operation name="add">
    <soap:operation soapAction=""></soap:operation>
    <input>
        <soap:body use="literal"></soap:body>
    </input>
    ...
</operation>

						

Had you been using RPC and not document, the values would have been passed separately to the operation invocation just like method parameters.

The @RequestWrapper and @ResponseWrapper annotations capture information that JAXB needs to perform the marshaling and unmarshaling operations. If your service is defined as using document/literal mode, this annotation also serves to resolve overloading conflicts.

6.1.2.  Create a client application using these artifacts.

Here are the steps in their simplest form.

First, write the invoker called CalculatorInvoker.java. Navigate to the top-level directory that you passed to the wsimport tool earlier. This was "/home/mz". You will write your client there.

import org.me.calculator.*;
public class CalculatorInvoker {
    public static void main(String... arg) {

        CalculatorWSService service = new CalculatorWSService();

        CalculatorWS port = service.getCalculatorWSPort();

        int result = port.add(2, 3);

        System.out.println("Result: " + result);
    }
}
						

The class in ecxample above simply creates a service instance, uses it to get the port, and uses the port to call the business method, add. Let's compile it, making sure the generated classes in the current directory are on your classpath:


>javac -cp . CalculatorInvoker.java

						

Then you can run it:


>java -cp . CalculatorInvoker

Result: 5

There are a few considerations to keep in mind with using generated clients:

  • The client cannot create or destroy web service implementations and has no view into its life cycle, which is handled entirely on the server.

  • A port object has no identity. It cannot meaningfully be compared to other port objects. You cannot ask for a specific instance of a port.

  • Treat service invocations as stateless. There is no mechanism within Service to handle state across requests.

  • All data binding is performed by JAXB.

6.1.2.1.  Invoke web service synchronously or asynchronously.

Synchronous clients

By using the synchronous model, you can develop SOAP-based web service client code without worrying about the underlying protocol details. Example below shows generated Service subclass:

package by.boot.java;

import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceFeature;

@WebServiceClient(
    name = "HelloMessengerService",
    targetNamespace = "http://java.boot.by/",
    wsdlLocation = "http://localhost:9999/Hello?wsdl")
public class HelloMessengerService extends Service {

    private final static URL HELLOMESSENGERSERVICE_WSDL_LOCATION;

    static {
        URL url = null;
        try {
            url = new URL("http://localhost:9999/Hello?wsdl");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        HELLOMESSENGERSERVICE_WSDL_LOCATION = url;
    }

    public HelloMessengerService(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    public HelloMessengerService() {
        super(HELLOMESSENGERSERVICE_WSDL_LOCATION,
            new QName("http://java.boot.by/", "HelloMessengerService"));
    }

    @WebEndpoint(name = "HelloMessengerPort")
    public HelloMessenger getHelloMessengerPort() {
        return (HelloMessenger) super.getPort(
            new QName("http://java.boot.by/", "HelloMessengerPort"),
            HelloMessenger.class);
    }

    @WebEndpoint(name = "HelloMessengerPort")
    public HelloMessenger getHelloMessengerPort(WebServiceFeature... features) {
        return (HelloMessenger) super.getPort(
            new QName("http://java.boot.by/", "HelloMessengerPort"),
            HelloMessenger.class,
            features);
    }
}

							

You can tell that class above is a JAX-WS-generated client because of its class-level @WebServiceClient annotation. The class has two constructors:

  • The first constructor is the default constructor. It configures the service so that any dynamic proxies created from it are produced by using the WSDL document that was used to generate the client code.

    In the HelloMessenger example, the tool was not instructed to create a local copy of the WSDL document. This is why there is an absolute reference to the actual URL at which the Endpoint publisher makes the WSDL document available. Because this is not recommended, make sure that you generate the web service client code so that it is copied to the client.

    One of the implications of not having a local WSDL document is that the constructor throws an exception in cases where the JAX-WS run time cannot connect to the server that is exposing the document.

  • The second constructor initializes the service by using a specified WSDL document.

In addition to these two constructors, the generated client has a couple of getHelloMessenger methods with which you can get a dynamic proxy that binds to the specified web service endpoint. The client uses the default constructor to connect to instantiate the web service:

HelloMessengerService service = new HelloMessengerService();
							

This approach can present a problem when you want the client application to switch from the test environment's web service endpoint to the production environment's web service endpoint. There are a couple of ways to override this endpoint location from your client code:

  • Use the overloaded constructor of the generated javax.xml.ws.Service subclass that takes another WSDL document location. This supplied WSDL document can, in turn, specify the service endpoint location of interest.

  • Use the default constructor, but specify the endpoint location on the dynamic proxy returned by the service.

After the HelloClient application has obtained a HelloMessengerService instance, it uses that instance to obtain a dynamic stub, which binds to the actual web service endpoint. Example below shows the generated web service client type that is implemented by the stub:

package by.boot.java;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

@WebService(name = "HelloMessenger", targetNamespace = "http://java.boot.by/")
@XmlSeeAlso({
    ObjectFactory.class
})
public interface HelloMessenger {
    @WebMethod
    @WebResult(targetNamespace = "")
    @RequestWrapper(localName = "sayHello",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHello")
    @ResponseWrapper(localName = "sayHelloResponse",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHelloResponse")
    public String sayHello(
        @WebParam(name = "arg0", targetNamespace = "")
        String arg0);
}
							

The HelloMessenger type is an ordinary Java interface with some JAX-WS-specific annotations. Although the return type of the HelloMessengerService is of this interface type, in reality what is returned is a dynamic stub that implements this interface.

Example below shows a HelloMessenger client application that explicitly specifies, on the dynamic stub, a new web service endpoint location:

import by.boot.java.HelloMessenger;
import by.boot.java.HelloMessengerService;

import javax.xml.ws.BindingProvider;

public class HelloClientCustomEndpoint {

    public static void main(String... args) throws Exception {

        HelloMessengerService service = new HelloMessengerService();
        HelloMessenger port = service.getHelloMessengerPort();

        ((BindingProvider)port).getRequestContext().put(
            BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://hello.com:69693/Hello"
        );

        String message = port.sayHello("Mikalai");
        System.out.println(message);
    }
}
							

The client-relevant code is highlighted in bold. The application casts the dynamic web service port proxy to a javax.xml.ws.BindingProvider. The BindingProvider is implemented by the dynamic client proxies and gives you access to the request and the response contexts. The application specifies the endpoint address on the request context using the BindingProvider.ENDPOINT_ADDRESS_PROPERTY property.

Asynchronous clients

The asynchronous client programming model in JAX-WS is merely a convenient functionality for developing web service clients. It does not refer to real asynchronous message exchanges. You can create asynchronous clients by configuring the tool that you use to generate JAX-WS web service client code.

JAX-WS offers two asynchronous programming models:

  • Polling clients.

  • Callback clients.

These approaches merely differentiate, in the Java method, signatures that are generated on the client-side web service port interface. When you enable asynchronous clients in your tool, JAX-WS generates three methods for every operation that is defined in the web service portType:

  • One-way asynchronous method.

  • An asynchronous polling method.

  • An asynchronous callback method.

Example below shows the HelloMessenger client-side endpoint interface that is generated when asynchronous method generation is activated by the JAX-WS tool:

package by.boot.java;

import java.util.concurrent.Future;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.Response;
import javax.xml.ws.ResponseWrapper;

@WebService(name = "HelloMessenger", targetNamespace = "http://java.boot.by/")
@XmlSeeAlso({
    ObjectFactory.class
})
public interface HelloMessenger {

    @WebMethod(operationName = "sayHello")
    @RequestWrapper(localName = "sayHello",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHello")
    @ResponseWrapper(localName = "sayHelloResponse",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHelloResponse")
    public Response<SayHelloResponse> sayHelloAsync(
        @WebParam(name = "arg0", targetNamespace = "")
        String arg0);

    @WebMethod(operationName = "sayHello")
    @RequestWrapper(localName = "sayHello",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHello")
    @ResponseWrapper(localName = "sayHelloResponse",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHelloResponse")
    public Future<?> sayHelloAsync(
        @WebParam(name = "arg0", targetNamespace = "")
        String arg0,
        @WebParam(name = "asyncHandler", targetNamespace = "")
        AsyncHandler<SayHelloResponse> asyncHandler);

    @WebMethod
    @WebResult(targetNamespace = "")
    @RequestWrapper(localName = "sayHello",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHello")
    @ResponseWrapper(localName = "sayHelloResponse",
        targetNamespace = "http://java.boot.by/",
        className = "by.boot.java.SayHelloResponse")
    public String sayHello(
        @WebParam(name = "arg0", targetNamespace = "")
        String arg0);
}
							

The method signatures are highlighted in bold. The first signature is used to create asynchronous polling clients. The second signature is used to create asynchronous callback clients. The third signature is used to create synchronous clients and is the signature that is used by the HelloClient application.

Polling clients

The polling client programming model refers to the usage of the asynchronous method that returns a typed javax.xml.ws.Response. Example below shows an asynchronous HelloMessenger web service client application:

import javax.xml.ws.Response;
import by.boot.java.HelloMessenger;
import by.boot.java.HelloMessengerService;
import by.boot.java.SayHelloResponse;

public class HelloAsyncPollingClient {

    public static void main(String... args) throws Exception {

        HelloMessengerService service = new HelloMessengerService();
        HelloMessenger port = service.getHelloMessengerPort();

        Response<SayHelloResponse> sayHelloAsync = port.sayHelloAsync("Mikalai");

        while ( ! sayHelloAsync.isDone() ) {
            // Do something useful for now
        }

        // Web service endpoint has now responded:
        SayHelloResponse sayHelloResponse = sayHelloAsync.get();
        String message = sayHelloResponse.getReturn();

        System.out.println(message);
    }
}
							

The relevant code is highlighted in bold. The client application invokes the sayHelloAsync method, which returns a response object. This object provides methods to query for response arrival, cancel a response, and get the actual response. The application, in this case, performs a busy wait, looping until the Response.isDone() method returns true, which indicates that the response has been received. The application then fetches the response by using the get() method. This method returns the response wrapper element that contains the actual method return value, which in this case is a simple java.lang.String object. If an endpoint throws a service exception, the get() method can throw a java.util.concurrent.ExecutionException, which can then be queried for the cause.

Callback clients

The callback client programming model refers to the usage of the asynchronous method that accepts an input parameter of a typed javax.xml.ws.AsyncHandler. Example below shows an asynchronous callback HelloMessenger web service client application:

import by.boot.java.HelloMessenger;
import by.boot.java.HelloMessengerService;
import by.boot.java.SayHelloResponse;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

public class HelloAsyncCallbackClient {

    public static void main(String... args) throws Exception {

        HelloMessengerService service = new HelloMessengerService();
        HelloMessenger port = service.getHelloMessengerPort();
        
        port.sayHelloAsync("Mikalai", new AsyncHandler<SayHelloResponse>() {
            public void handleResponse(Response<SayHelloResponse> res) {
                try {
                    SayHelloResponse response = res.get();
                    String message = response.getReturn();
                    System.out.println(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
    }
}
							

The application passes in an anonymous inner class of type AsyncHandler. This class has a method called handleResponse that is invoked by the JAX-WS runtime when the message is received. The argument to this method is of the same type that is used for the polling method, a typed response object.

Example above does not show that you can save the return value of the asynchronous method invocation into a java.util.concurrent.Future:

Future<?> future = port.sayHelloAsync("Mikalai", ...);
							

As is the case with the polling response object, you can query this object to obtain status and possibly cancel the operation.

6.1.3.  Package and deploy accordingly.

blah-blah

Professional hosting         'Oracle Certified Expert Web Services Developer 6' Quiz     Free SCDJWS 5.0 Guide