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 .
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.
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.
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.
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.
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.
If you want manual control over how the service is invoked, you must call the invocation methods yourself. There are three of these.
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 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 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.
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.