Accessing Web Services From a Stand-Alone Java Client

Inderjeet Singh

Problem Description

Some web services are designed with a rich client user interface in mind. An example could be an Internet chat web service that can use a GUI to provide alerts, video-conferencing, and other such capabilities. Some of the business to consumer services may also benefit from rich user interfaces, for example, a mail and calendaring service. The Java[TM] Swing APIs provide a powerful, portable mechanism to create such rich stand-alone desktop clients. This solution discusses some issues to keep in mind while accessing a web service from a stand-alone Java client.

While accessing a web service from a stand-alone Java client, some considerations are

Solution

The following table summarizes the recommendations for accessing web services through standalone clients. Details on these recommendations are provided in the subsequent text.

Issue
Recommendation
Choosing the communication mode Use stubs. Generate service interface from WSDL in WS-I basic profile compatible mode (using -f:wsi w) with unwrapped parameters with -f:unwrap.
Making JAXRPC runtime available Include JAXRPC implementation jars from the application server.
Handling errors Involve user to retry or select alternate service for system exceptions, and to take corrective action for service-specific exceptions.
Decoupling application logic from web service artifacts Use a delegate to encapsulate all calls to the web service and the parameters used for the web service.
Avoiding delays because of web service overheads to create responsive GUIs Make coarse-grained calls to the web service, cache received data and use it for local accesses.
Deploying clients automatically Use Java Web Start with the stand-alone clients to make them available through a web site.

Now let us look at each of these issues in detail and begin with the first, which is the choice of the communication mode.

Choosing the Communication Mode

The communication mode most suitable for stand-alone clients is stubs because it is easiest to use when compared with DII or dynamic proxies. See Section 5.3.1 and Section 5.3.2 of the book Designing Web Services with the J2EE 1.4 Platform for a comparison of these approaches.

Using Stubs

The stub is generated by running the JAX-RPC compiler on the service WSDL. While generating the stub, ensure that the JAX-RPC compiler is configured to use WS-I basic profile compatible document-literal encoding (achieved by using the -f:wsi switch), and is also set to unwrap the parameters (achieved by using the -f:unwrap switch). This can also be done in an ant target. For example, Code Example 1 generates the stub classes from a WSDL file:

   <taskdef name="wscompile" classname="com.sun.xml.rpc.tools.ant.Wscompile">
      <classpath refid="jaxrpc.classpath"/>
    </taskdef>
    <wscompile gen="true" base="${output.classes.dir}" features="explicitcontext,wsi,unwrap" keep="true" debug="true" config="${jaxrpc.client.config.dir}/client-config.xml">
      <classpath refid="jaxrpc.classpath"/>
    </wscompile>

    Code Example 1: Ant task to Generate Stub Classes

In the example above, the client-config.xml file contains a pointer to the location of the WSDL file. Also note that the keep attribute is set to true to ensure that the Java source code for the generated classes for the stub are preserved. This source code is needed because this is how a client developer will know what the generated stub interface is, and how it should be used. The generated Java source code can then be used to understand the stub and its associated classes. A client developer can either use this source code directly, or generate Javadoc[TM] code from it to understand how to make calls to the service. The 

The standalone client instantiates and configures the generated stub class to access the web service. Code Example 2 illustrates how to accesses a web service using stubs:

    StringPurchaseOrderService_Impl svc = new StringPurchaseOrderService_Impl();
    StringPurchaseOrderServiceSEI poservice = svc.getStringPurchaseOrderServiceSEIPort();
    ((Stub)poservice)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY,
            "http://localhost:8080/webservice/StringPurchaseOrderService");
    String purchaseOrder = ... // This is the string representation of a purchase order in XML
    String result = poservice.submitPO(purchaseOrder);

    Code Example 2: Using Stubs With Stand-Alone Clients

Note that the StringPurchaseOrderService_Impl is not portable across different JAX-RPC runtimes, but is the recommended choice because it is significantly easier to use than any of the portable alternatives. This is different from the case of J2EE clients, where it is possible to write fully portable code. It is also important to point out that the standalone client itself is portable from Java platform point of view: it can be used unchanged on a variety of hardware platforms and operating systems. It just requires the same JAX-RPC runtime environment, which must be bundled with the application because JAX-RPC is not yet a part of the Java SE platform. The portability of stub classes is one of the stated goals of the JAX-RPC 2.0 specification which will be available in the Java EE 5 platform.

Often a client would prefer not to hardcode the URL of the web service it is using. The client might choose to select the service URL at runtime, sometimes based on user input. This can be done with stubs by setting the Stub.ENDPOINT_ADDRESS_PROPERTY to the desired service URL at runtime as shown in the example above.

Using Dynamic Proxy

Dynamic proxy should be used when stubs cannot be used because of their non-portable nature. This might happen when the JAX-RPC implementation available at the runtime is different from that of the compile time. Code Example 3 below illustrates how dynamic proxy can be used with stand-alone clients:

    ServiceFactory sf = ServiceFactory.newInstance();
    URL wsdlURL = new URL("
http://localhost:8080/webservice/StringPurchaseOrderService?WSDL");
    QName serviceQname = new QName("urn:StringPurchaseOrderService", "StringPurchaseOrderService");
    Service s = sf.createService(wsdlURL, serviceQname);
    QName portQname = new QName("urn:StringPurchaseOrderService", "StringPurchaseOrderServiceSEIPort");
    StringPurchaseOrderServiceSEI poservice = (StringPurchaseOrderServiceSEI) s.getPort(portQname, StringPurchaseOrderServiceSEI.class);
    String purchaseOrder = ... // This is the string representation of a purchase order in XML
    SubmitPO param = new SubmitPO(purchaseOrder);
    SubmitPOResponse response = poservice.submitPO(param);
    String result = response.getResult();

    Code Example 3: Using Dynamic Proxies With Stand-Alone Clients

Note that the dynamic proxy needs to use the wrapped version of the service endpoint interface instead of the more convenient unwrapped version. This is because the web service is using the WS-I basic profile compliant document  instead of the rpc style SOAP binding. The document binding maps to a wrapped version of the interface, and the wscompile adds a convenient way to access an unwrapped version through the stub class.  However, the dynamic proxy does not use the stub class and therefore has to use the wrapped version. The wrapped version of the service endpoint interface can be generated by omitting the unwrap switch while invoking wscompile to generate the classes from the WSDL. Note that the dynamic proxy code still requires the generation of classes from the WSDL. However, it needs access to only the classes that relate to the service endpoint interface such as StringPurchaseOrderServiceSEI, SubmitPO, and SubmitPOResponse.

Using Dynamic Invocation Interface (DII)

The scenarios for using DII are rare. It can be used in situations where the client does not have access to the WSDL but somehow knows which methods to call. Code Example 4 below illustrates how DII can be used with stand-alone clients:

    ServiceFactory sf = ServiceFactory.newInstance();
    URL wsdlURL = new URL("
http://localhost:8080/webservice/StringPurchaseOrderService?WSDL");
    QName serviceQname = new QName("
urn:StringPurchaseOrderService", "StringPurchaseOrderService");
    Service s = sf.createService(wsdlURL, serviceQname);
    QName portQname = new QName("
urn:StringPurchaseOrderService", "StringPurchaseOrderServiceSEIPort");

    Call call = s.createCall(portQname);
    call.setTargetEndpointAddress(serviceUrl);
    call.setProperty(Call.SOAPACTION_USE_PROPERTY, new Boolean(true));
    call.setProperty(Call.SOAPACTION_URI_PROPERTY,"");

    // Note that the operation name need not be set by calling
    // call.setOperationName(new QName(NS_BODY, "submitPO"));
    // This is because the SOAP binding used by the Web service is document, not rpc.
 
    // For WS-I compliant document-literal, need to set the encoding style
    // to literal by specifying "" as the encoding, and the operation style to document
    call.setProperty("javax.xml.rpc.encodingstyle.namespace.uri", "");
    call.setProperty(Call.OPERATION_STYLE_PROPERTY, "document");

    // The types for the request parameter and return value are defined in the
    // WSDL file itself, so their qnames are defined with the namespace of the body
    QName requestQname = new QName("
urn:StringPurchaseOrderService", "submitPO"); 
    QName responseQname = new QName("urn:StringPurchaseOrderService", "submitPOResponse");

    // Define the type of the return value for the DII call.
    // SubmitPOResponse must match the wrapped type sent by the Web service.
    call.setReturnType(responseQname, SubmitPOResponse.class);

    // Define the type of the method parameter for the DII call.
    // In the WSDL file, the name of the message part for submitPO is "parameters"
    // Hence the request parameter is defined in this way.
    call.addParameter("parameters", requestQname, SubmitPO.class, ParameterMode.IN);
    String purchaseOrder = ... // This is the string representation of a purchase order in XML
    SubmitPO param = new SubmitPO(purchaseOrder);
    Object[] params = {param};

    // Invoke the DII call
    SubmitPOResponse response = (SubmitPOResponse) call.invoke(params);
    String result = response.getResult();

    Code Example 4: Using DII with stand-alone clients

As shown in the example above, using DII can be very complex and therefore should only be used in exceptional situations. The JAX-RPC implementation available in the J2EE 1.4 SDK also contains a bug where it does not generate the wrapper class correctly when the parameter type for the web service method is of the type string. The developers must work around this bug by overwriting the generated wrapper class SubmitPO with an equivalent class that renames the member variable to be String_1 instead of string_1.

Making JAX-RPC Runtime Available

The JAX-RPC runtime also must be included with the application. This can be done by copying the necessary classes from the application server installation. While doing this bundling, be aware of the licensing implications when redistributing your application. The ant build file fragment presented in Code Example 5 illustrates how this can be done with J2EE SDK.

  <target name="copy-jaxrpc-runtime" depends="init">
    <unjar src="${j2ee.home}/lib/j2ee.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/jaxrpc-api.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/jaxrpc-impl.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/saaj-impl.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/mail.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/dom.jar" dest="${build.classes.dir}"/>
    <unjar src="${j2ee.home}/lib/xercesImpl.jar" dest="${build.classes.dir}"/>
  </target>

  Code Example 5: Ant task to Copy JAXRPC Runtime

Handling Errors

A web service client must deal with two type of errors: system exceptions and service-specific exceptions.  The system exceptions occur because of irrecoverable system errors such as a faulty network connection, or if the web service is down. The JAX-RPC clients receive the system exceptions as RemoteException. The service-specific exceptions are thrown by the web service to indicate an error in the application logic such as if an incorrect parameter was passed, or if an attempt is made to create a duplicate key in a database. The JAX-RPC clients receive the service-specific exceptions as application exception classes. These exception classes are generated by the JAX-RPC compiler from the faults specified in the WSDL.

Exception handling for stand-alone clients is often simpler than the case where the web service call was made by a servlet or an enterprise bean. This is because the stand-alone clients often have a human user at their end who can act on an error condition. Thus, a simple exception handling strategy may involve translating exception conditions to messages that a human user will understand, and then prompt the user to either retry the operation, or take corrective action. Often service-specific exceptions can be avoided or reduced by ensuring that the parameters are validated before making the web service call.

Decoupling Application Logic From Web Service Artifacts

In a good design, the application logic of the web service client will have minimal dependencies on the service WSDL. This is useful because as the service evolves, the impact on the client code will be minimal. This decoupling can be achieved by creating a delegate class that encapsulates code that uses the stub and its generated classes. The public API of the delegate does not expose any of the stub classes. Instead, it maps them to classes that the rest of the application understands.

Caching Data Locally

Typical swing clients provide a rich user interface that is very responsive to the user. The swing clients may choose to cache data locally instead of going over the network to increase the responsiveness of the user interface. However, this introduces the issue of maintaining data consistency. Both the client and the web service need to be designed to use a protocol (such as SyncML) to keep the data synchronized.

Using Java Web Start for Deployment

One of the primary reasons why browser-based clients are preferred over desktop clients is because they can be accessed from any machine that is connected to the Internet without first requiring installation. The deployment of stand-alone Java clients can be managed by using the Java Web Start technology that is included in the Java platform. However, there are some issues involved in using Java Web Start technology with web service clients:

References

For more information about this topic, refer to the following:


© Sun Microsystems 2005. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.