Introduction

This is a guide to developing Java clients to InstantSOAP services. It will walk you through several different ways of invoking services, with varying levels of control. By the end of this tutorial, you should be able to write clients using both the blocking and non-blocking APIs, and to handle asynchronous services in a number of different ways.

This guide assumes that you have run through the Getting Started guide and have deployed the echo-test war at the default location on the current machine. We also assume that you will be developing your client using CXF as your web-services toolkit. We also have a tutorial covering a low-level, no-frills client .

Building the client-side web-service object

To access the web-service located on the server, you must create a client-side object that proxies calls to it. Luckily, CXF makes this relatively easy, by delegating much of the hard work to the Spring Framework .

To get the application started, you will need some imports and general bioler-plate code.

import uk.ac.ncl.cs.instantsoap.wsapi.*;
import static uk.ac.ncl.cs.instantsoap.wsapi.Wsapi.*;
import org.apache.cxf.jaxws.*;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
import java.util.Map;

public class IntrospectingClient
{
  public static void main(String[] args)
          throws Throwable
  {

The client is specified in a spring configuration file, and is loaded into your java application. The java code looks like this:

    ClassPathXmlApplicationContext context
            = new ClassPathXmlApplicationContext(new String[] {"client-beans.xml"});

    WebServiceDispatcher dispatcher = (WebServiceDispatcher) context.getBean("client");

This will search for a file called client-beans.xml in the classpath of your application and extract the bean called client from there. To ensure that this works, we must make sure that client-beans.xml does provide the required stuff. The entire file looks like this:

<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/schema/jaxws.xsd">

  <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
    <property
            name="serviceClass"
            value="uk.ac.ncl.cs.instantsoap.wsapi.WebServiceDispatcher"/>
    <property
            name="address"
            value="http://localhost:8080/instantsoap-ws-echotest-1.0/services/instantsoap/applications"/>
  </bean>

  <bean id="client" class="uk.ac.ncl.cs.instantsoap.wsapi.WebServiceDispatcher"
        factory-bean="clientFactory" factory-method="create"/>
  
</beans>

The xml file is wrapped in a beans element. This can contain many different kinds of elements. In this example, it contains the descriptions of two beans. The first bean is the client factory. It is responsible for manufacturing clients from the information given to it. This includes the URL of the web service and the interface specifying the Java binding for this web service.

<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
  <property
          name="serviceClass"
          value="uk.ac.ncl.cs.instantsoap.wsapi.WebServiceDispatcher"/>
  <property
          name="address"
          value="http://localhost:8080/instantsoap-ws-echotest-1.0/services/instantsoap/applications"/>
</bean>

The seccond bean is the client. This is the actual Java object that will implement the service interface by contacting the service at the specified URL.

<bean id="client" class="uk.ac.ncl.cs.instantsoap.wsapi.WebServiceDispatcher"
      factory-bean="clientFactory" factory-method="create"/>

By modifying this spring context, you can alter how the client is configured. For example, you can inject ws-security, or log the transactions. For further information, refer to the CXF documentation.

Introspecting the InstantSOAP Service

Now that you have your web service client, you can call methods on the InstantSOAP service. The first thing you may want to do is introspect what applications are deployed. There are four commands for doing this.

Application Introspection
Returns Method Description
List<String> listApplications() List all of the names of the applications known to this service
MetaData describeApplication(String appName) Return a description of the application with this name
List<MetaData> getInputs(String application) Describe each of the named inputs to the application
List<MetaData> getOutputs(String application) Describe each of the named outputs from the application

We can retrieve a list of all applications accessible through dispatcher using a call to listApplications.

System.out.println("Fetching a list of all applications");
List<String> appNames = dispatcher.listApplications();
System.out.println("There are " + appNames.size() + " applications loaded");

Working through this list, we can fetch the MetaData associated with each application by calling describeApplication for each in turn. This will tell us the name (which should match the name we already have) and a description. It may optionally also contain semantic annotation, in an unspecified format, most likely raw XML.

for(String appName : appNames)
{
  MetaData appMD = dispatcher.describeApplication(appName);
  System.out.println(appMD.getName() + ": " + appMD.getDescription());

For each of these applications, we can also retrieve the inputs and the outputs that they honor. Inputs are named values that must be supplied to launch the application. Inputs are named values that will be returned subject to a sucessfull execution.

      System.out.println("  Inputs:");
      for(MetaData imd : dispatcher.getInputs(appName))
      {
        System.out.println("\t" + imd.getName() + ": " + imd.getDescription());
      }

      System.out.println("  Outputs:");
      for(MetaData omd : dispatcher.getOutputs(appName))
      {
        System.out.println("\t" + omd.getName() + ": " + omd.getDescription());
      }

To make the application so far compile, you need to close all curly braces and perhaps print out a little more info.

      System.out.println();
    }

    System.out.println("Done!");
  }
}

The code so far in this guide should be complete to compile and run. You can obtain the compete source for this example, including the pom, in tar.gz or zip formats.

Simple Client

You have already seen the minimal client implementation in Getting Started . Here we will pull this appart a bit, to show what is going on. The part of the client that did the work was as follows:

JobSpecification js = jobSpecification(
        "stringEcho",
        pair("messageIn", "Hi Mum!"));

Map<String, String> results = invoke(dispatcher, js);
System.out.println("The message was: " + results.get("messageOut"));
      

The first statement here sets up a JobSpecification to describe what application you want to be run and what inputs it will be given. The next line invokes the application using a built-in static utility method to hide any complexity. The next line prints out the result.

JobSpecification properties
Property Type Description
application String The name of the application to invoke
inputs Map<String, String> Values for each of the requried named inputs

The jobSpecification method is a convenient way to make new job specifications, provided by the static utility class Wsapi. Many of the constructors in InstantSOAP are wrapped by static methods intended for static import like this.

The first argument to jobSpecification will be used for the application property of the resulting JobSpecification bean. The method then expects a list of key/value pairs, used to build the inputs map of the bean. This same code could have been written out in full.

Map<String, String> inputs = new HashMap<String, String>();
inputs.put("messageIn", "Hi Mum!");
JobSpecification js = new JobSpecification("stringEcho", inputs);
      

The messageIn input is required by the stringEcho" application. This would have been listed and described when you ran the introspection client earlier .

The invoke method is also provided by a static import from Wsapi. This will use the provided dispatcher to invoke the application as described by the job spec. It blocks the client thread until the operation has been handled. However, it communicates asynchronously with the server to avoid issues with timeouts.

Invocation Methods

If you want manual control over how the service is invoked, you must call the invocation methods yourself. There are three of these.

Job Invocation
Method Description
invokeAndBlock Invoke the application, blocking the client thread and keeping the http transaction alive untill a response containing either the result or an exception are returned.
invokeNonBlocking Invoke the application, returning control to the client emediately, and allowing the server to asynchronously process the request.
pollForResponse Attempt to retrieve the result of an asynchronously executing process.

The invokeAndBlock method is recomended only for applications that you are sure will run quickly. It removes the overhead of mulitple SOAP transactions, but runs the risk of failing due to transport time-outs for longer-running applications.

The invokeNonBlocking and pollForResponse methods are used in conjunction to implement non-blocking invocations. Firstly, invokeNonBlocking is used to launch the application server-side. The result of this is either the result of the application if the server could execute it emediately, or (more likely) information describing when to call pollForResponse. This will also either return the result or polling information. The client should call pollForResponse repeatedly untill either a result or an error is returned. This mechanism removes the likelehood of transport timeouts, at the cost of more SOAP transactions.

Using Blocking Invocation

Using invokeAndBlock is straightforward. Simply invoke the method, and check the return value for the result map.

BlockedInvocationResponse response = dispatcher.invokeAndBlock(js);
System.out.println("The message was: "
        + response.getResults().get("messageOut"));

The first statement invokes the web service, recieving back a BlockedInvocationResponse bean. This has two properties, UUID and result. The UUID is the unique ID of the application invocation. This may be useful for cross-referencing this invocation against other resources and services, such as those providing provenence. The result property contains the named application outputs.

You can obtain the compete source for this example, including the pom, in tar.gz or zip formats.

Using Non-blocking invocation

Using invokeNonBlocking is a little more work than invokeAndBlock. The basic framework involves an initial call and then a while-loop to handle the polling.

        NonBlockingInvocationResponse response = dispatcher.invokeNonBlocking(js);

        while(response.getJobStatus() == JobStatus.JobPending)
        {
            response.getUnit().sleep(response.getWait());
            response = dispatcher.pollForResult(response.getUuid());
        }

        System.out.println("The message was: "
                + response.getResults().get("messageOut"));

The first statement calls invokeNonBlocking to get a NonBlockingInvocationResponse bean. This has a number of properties.

NonBlockingInvocationResponse properties
Type Name Description
JobStatus status The status of the job as reported by the last web service invocation. This will be one of JobCompleted and JobPending.
UUID uuid The UUID of the application invocation. This will stay constant throughout the lifetime of this invocation, regardless of which web-service calls are being used.
int wait How long to wait for before the server would like you to try to poll for a response.
TimeUnit unit The time unit used to interpret the wait property. This allows the server to specify a wide range of intervals.
Map<String, String> results The result of the application invocation.

If status is JobPending then the values of UUID, wait and unit are valid. If status is JobCompleted then the values of UUID and results are valid.

Once the client has an initial NonBlockingInvocationResponse bean, it checks the value of jobStatus. While this is in the pending state, it sleeps for the requested length of time, and then polls for a new response. When the response is no longer in the pending state, the result is ready and the client prints it out.

In a client that is managing multiple invocations, this while-loop can be decomposed into a series of scheduled tasks managed by a scheduling execution service. This would allow a single client thread to service a large number of asynchronous application invocations. However, the logic would be identical to this example.

You can obtain the compete source for this example, including the pom, in tar.gz or zip formats.