You are on page 1of 55

Systinet Developer's Corner Tutorial

Systinet Server for Java 5.0 - Tutorial 2

Asynchronous Web Services


Petr Dvorak (petr.dvorak@systinet.com)

' Systinet 2003 - 2004, All Rights Reserved


Table of Contents
Introduction ......................................................................................................................................... 1
1. The Tutorial Package .................................................................................................................. 1
2. Asynchronous invocation ............................................................................................................ 2
The Application .................................................................................................................................... 3
The Service .......................................................................................................................................... 5
1. Turning the Chat Server into a Runtime Web Service ....................................................................... 5
1.1. Build and Run the Chat Server ........................................................................................... 5
2. Persistent Deployment of the Chat Service ...................................................................................... 6
2.1. Packaging the Service ...................................................................................................... 7
2.2. Deploying the Service ...................................................................................................... 8
Sync and Async Clients ........................................................................................................................ 11
1. The Synchronous Client ............................................................................................................ 11
1.1. Generating the Client ..................................................................................................... 11
1.2. Running the Client ......................................................................................................... 12
1.3. Intermezzo - SOAP faults ................................................................................................ 13
2. Asynchronous API ................................................................................................................... 14
3. Asynchronous Transport ............................................................................................................ 17
Conclusion ......................................................................................................................................... 21
A. Code Samples ................................................................................................................................. 23
A.1. Application Code .................................................................................................................. 23
A.2. Service Code ........................................................................................................................ 34
A.3. Client Code ......................................................................................................................... 35
B. Legal ............................................................................................................................................. 51
B.1. Copyright ............................................................................................................................ 51
B.2. Disclaimer ........................................................................................................................... 51
B.3. Trademarks .......................................................................................................................... 51

Page iii
Page iv
Introduction
In Part One of this tutorial series, I created a simple SOAP-based chat server and client using the Systinet Server for
Java (SSJ) platform. In this the second part I'll show you how easy it is to call the services asynchronously. As a small
bonus, I'll touch on the problems of transmitting exceptions over SOAP.

The second part of the tutorial starts where the first part ended. If you are not yet familiar with the Chat Server you can
review and download Tutorial 1 [http://dev.systinet.com/library/tutorials/ChatService/index] from Systinet's Developers'
Corner. There I provided a light introduction to the world of Web services. We created a simple SOAP implementation
of our Simple Chat server. We also created a client that exercises the Chat server's services. This second tutorial extends
the original concept of the Simple Chat server with asynchronous messaging. It is expected that you've already read the
first tutorial before you start with this one. Knowledge of Java is also essential.

1. The Tutorial Package


If you are reading this document on-line and haven't already done so, you should start by downloading the tutorial package
best suited for your system.


chat_async_tutorial.zip [http://dev.systinet.com/get&file=chat_async_tutorial.zip] - packaged for Windows


chat_async_tutorial.tgz [http://dev.systinet.com/get&file=chat_async_tutorial.tgz] - packaged for Linux

The directory into which you unpack the contents of these packages we will henceforth refer to as DEMO_HOME.

The download package is structured as follows:


DEMO_HOME
+-bin
+-doc
+-pdf
+-html
+-src

The bin/ directory includes a build.xml file and a run.bat or run.sh script. These are used with ANT for building,
deploying and executing our service and client applications.

When working with persistent deployment, the build scripts were developed with an unsecure SSJ test server in mind.
However, if you will be deploying to a secure server make sure the admin.name and admin.pass properties are set correctly
in the bin/ant.properties file. They should match the credentials of an SSJ administrator account with permissions to
deploy and undeploy services from the container. A more compete discussion follows in Section 2, Persistent Deployment
of the Chat Service.

The details of working with Security in SSJ is the subject of the fourth tutorial in this series.

The doc/ directory includes two sets of the documentation you are reading now.

src/ includes all the source code presented in this discussion.

As we work through these tutorials, other directories will be generated, most importantly a build/ directory where byte
compiled Java class files will be written.

ANT. This tutorial relies on ANT for streamlining building and execution tasks. Provided and maintained by Apache,
ANT is an XML-based development system that has become a standard tool for organizing and compiling projects written
for Java and other languages. Systinet Server for Java comes with a core tools library (core_tools.jar) that makes it

Page 1
2. Asynchronous invocation

possible to plug routine Web services tasks, such as generating skeleton code from WSDLs and packaging services, directly
into ANT build files. We will explain and use these tasks as the need arises in the upcoming tutorial.

2. Asynchronous invocation
For those new to distributed computing as well as Web services, asynchronous invocation is among the most valuable
tools at a developer's disposal when it comes to fine tuning and removing bottlenecks from the system. In the ChatService
tutorial the client application process sent its request to the service and waited for the response message before continuing.
When high messaging latency can be expected in a network system, either because of traffic congestion, call failures, or
server-side delays in processing the response, this type of synchronous behavior can result in high performance penalties.
Sending a request and then moving the client process along to either later poll for the response or handle it as a callback
reduces traffic and makes a more efficient use of client and network resources. These and related approaches are what
we mean by "asynchronous invocation". For a more complete and very useful discussion see Jack Shirazi's "Java Perform-
ance Tuning"[1].

In the context of the SSJ framework, there are two levels of asynchrony, which are complimentary. The first one is focused
on the client's application logic, and is purely client-side efficiency driven. In this scenario, the client consumes a service's
business logic that has a long latency. The client doesn't want to wait idle for the response to arrive. Instead, it wants to
call the invocation framework in a non-blocking mode, letting the framework efficiently handle the long running transaction
and to then be notified when the result is ready. We call this dimension "API asynchrony", because it is often established
with a special non-blocking invocation API.

The second level focuses on the physical means of communication between the two parties; let's call it "Transport asyn-
chrony". In the transport synchronous scenario, two parties access an open direct communication channel for exchange
of messages. When using transport asynchrony there's no single channel for transmission of both the request and response
messages. Instead, the consumer uses one connection (or some messaging service) to send the request. The service then
processes the request, and uses another connection to send the response. A classical example of asynchronous transport
communication is the exchange of emails. Transport asynchrony lessens the differences between how "client" and
"server" are traditionally understood. The client itself must be able to behave like a server. It must have its own unique
address where it listens for the (response) message to arrive.

In the end, these two aspects create four combinations. All of them have their use case. Sync/sync is obvious. With HTTP
it is a quick and easy communications solution to implement. Asynchronous API over synchronous transport can be used
when it is affordable to keep the direct connection open for some (not exceedingly long) amount of time. Synchronous
API over asynchronous transports is used when there's no way to create a direct communication channel between the two
parties (firewall, limited availability of one of the parties, missing physical direct link, etc.). Finally, the asynchronous
API and asynchronous transport are used in a truly asynchronous loosely coupled solution, where the business transactions
can take a long time to complete. In this demo, I will model this fully asynchronous environment.

Table 1. Sync v. Async Advantages


API Sync. API Async.
Trans. Sync. Simple and straight forward to imple- Saves Client resources but not network
ment ones
Trans. Async. Useful when communication is unreli- True loosely coupled solution where
able or must be indirect (i.e. Firewall) processes can be long running and
traffic heavy

Page 2
The Application
The base line of this demo is the previous SimpleChatServer application. We'll simply break backward compatibility with
the old clients, and implement the service in a new Java package, using a new target namespace for the WSDL file. This
means we will need to create new clients (or modify the existing ones), but it lets us avoid clashes with the existing Sim-
pleChatServer.

To model long latency business logic in the chat server, we add a new method to the IChatService interface:
ChatMessage[] getOrWaitforNewMessages(long afterId, long
waitMillis) throws NoMessageArrivedException;

In our ChatService.java implementation of this interface we simply call the SimpleBackendImpl.java method getMes-
sagesAfter(long, long). The heart of this process is as follows.

public ChatMessage[] getMessagesAfter(long id, long timeout) {


...
//check if there are any newer messages
if (lastMessage != null && lastMessage.id > id) {
return getMessagesAfter(id);
}
//when no newer messages found, wait for the new message, or timeout
//it's no rocket science, let's make it as simple as possible
if (timeout < 0)
timeout = getDefaultTimeout();
try {
//wait for a message to arrive, or timeout
synchronized (this) {
wait(timeout);
}
} catch (Exception e) {
...
} finally {
//if anything has arrived, return it
if (lastMessage != null && lastMessage.id > id) {
return getMessagesAfter(id);
} else {
//nothing to return
return null;
}
}
}

This method immediately returns messages having an id larger than the afterId argument (i.e. the latest ones). If none are
present it then waits a short while for one or more new messages to be posted and then passes them along. This is quite
natural behavior for a chat server. The client doesn't have to periodically poll for new messages, as in every fraction of a
second. Instead, it places its request, and gets back the result when new messages arrive. This model does have its weak
spots. For instance, a maximum wait time parameter has been added to the call. In the interface implementation, ChatSer-
vice.java, when no message arrives within the specified amount of time (i.e. when the backend returns null) a NoMessage-
ArrivedException is thrown and returned to the client. Despite this imperfection, this design is good enough for our
tutorial purposes. It lets us demonstrate how to transmit exceptions over SOAP. We could have used the timeout facility
of the SSJ framework, but that wouldn't prove as instructive.

In total the application package contains four interface files and two implementation files.

• com.systinet.asyncchat.server.iface:

Page 3
• ChatMessage.java - a key message component.

• ChatPerson.java - a key message component.

• IChatService.java - interface definition.

• NoMessageArrivedException.java - custom exception to be discussed shortly.

• com.systinet.asyncchat.server.impl:

• ChatService.java - interface implementation, adapter to the backend.

• SimpleBackendImpl.java - a rudimentary driver for demonstrating and testing aspects of the service.

Page 4
The Service
1. Turning the Chat Server into a Runtime Web Service
There should be no surprises lurking here for those who are familiar with the first tutorial. We will publish the chat server
implementation as a Web service using the Systinet Server for Java runtime publishing API. With two method calls, the
ChatService class gets published as a Web service. A final part of the com.systinet.asyncchat.server.impl package,
the full source is here: ChatServerApp.java.
public class ChatServerApp {
public static void main(String[] args) throws Exception {
Wasp.startServer("http://localhost:6060");
Registry.publish("/ChatServiceAsync/", ChatService.class);
System.out.println("Chat server V2 is up an running.")
}
}

1.1. Build and Run the Chat Server


To build the chat server:

1. Make sure you are in the DEMO_HOME/bin directory.

Windows. c:\ > cd %DEMO_HOME%\bin

UNIX. ~/ $ cd ${DEMO_HOME}/bin

2. Make sure the WASP_HOME environment variable points to SSJ's Web Application and Services Platform.

Windows. i.e. c:\ > set WASP_HOME=c:\opt\wasp50

UNIX. i.e. ~/ $ export WASP_HOME=/opt/wasp50

Be sure the to replace the path in these examples with the one that actually leads to your SSJ installation.

3. Compile the service application.

Windows. DEMO_HOME\bin > run build_svc

UNIX. DEMO_HOME/src/server/src $ ./run.sh build_svc

4. Compile the runtime container application.

Windows. DEMO_HOME\bin > run build_srv

UNIX. DEMO_HOME/bin $ ./run.sh build.srv

5. Run the runtime server-container.

Windows. DEMO_HOME\bin > run run_srv

UNIX. DEMO_HOME/bin $ ./run.sh run_srv

If everything is configured correctly you should see the following message in the command line console:
To Interrupt the server hit [CTRL] + [c].
Chat server V2 is up an running.

Page 5
2. Persistent Deployment of the Chat Service

As a reminder, the server is up and running now. Its location is http://localhost:6060/ChatServiceAsync/. The location of
the dynamically generated WSDL document is http://localhost:6060/ChatServiceAsync/wsdl.

You are now ready to build and run the clients. The Persistent Deployment section which follows shows the alternative
declarative approach to deploying Web services. Since we will be using declarative deployment in future tutorials we
encourage you to work through this section carefully. However, if you are impatient to try the clients you might skip
ahead to the client section now and return to Persistent Deployment once you have learned about SSJ's asynchronous
APIs.

2. Persistent Deployment of the Chat Service


To avoid port conflicts and to work through this presentation make sure the runtime container has been terminated. In the
console in which it was loaded press CTRL + C.

A second option for Web service deployment is the declarative or persistent approach. This involves packaging the class
we want to use for our Web service, along with its dependent interfaces, classes and descriptor files, into a deployment
package. The deployment descriptor files declare to an active Systinet Server for Java just how the service should be de-
ployed and managed.

You can check to see if Systinet Server for Java is running in its default configuration on local host by trying to load its
Administration Console in any standard Web Browser. Its URL is http://localhost:6060/admin/console. If you get HTTP
404 you need to start Systinet Server for Java now.

1. Change to the WASP_HOME/bin directory

Windows. c:\ > cd %WASP_HOME%\bin

UNIX. /~ $ cd $WASP_HOME/bin

2. Run the serverstart shell script

Windows. %WASP_HOME%\bin > serverstart.bat

UNIX. $WASP_HOME/bin $ ./serverstart.sh

If you look at a listing for this directory you will see a number of scripts. Some are used to invoke the tools for deploying
services and creating clients. Important for our upcoming discussion are:

• WASPPackager. Used for generating Web service deployment packages.

• DeployTool. Used for contacting the server's deployment service and deploying Web service packages.

• WSDL2Java. Used for creating interface and skeleton code on the client side.

These tools are also available as ANT tasks through the com.idoox.wasp.tools.ant package. We will use these tasks
in deploying our service and preparing our clients instead of messing with their command line equivalents. To add an SSJ
task to your ANT project files simply include taskdef nodes in build.xml like the following:
<taskdef
classname="com.idoox.wasp.tools.ant.DeployTask"
name="Deploy"> <classpath>
<fileset dir="${wasp.home}/lib"
includes="wasp.jar"/> <fileset
dir="${wasp.home}/lib"
includes="core_tools.jar"/> <fileset
dir="${wasp.home}/lib"

Page 6
2.1. Packaging the Service

includes="core_services_client.jar"/>
</classpath> </taskdef>

Once this taskdef has been defined it can be used in any target in the build.xml file.

Note
One final note on using SSJ's ANT Tasks, be sure that the WASP_HOME/lib/core_tools.jar is in the CLASSPATH
used to invoke ANT.

2.1. Packaging the Service


Be sure to change your directory back to DEMO_HOME/bin.

Firest we must package the compiled service into a single archive file organized in such a way that it can be passed to the
SSJ Server, then taken apart and plugged into the Server's Registry. This task is handled by WaspPackager which is
wrapped inside the pkg_svc target in our ANT build file.

Windows. DEMO_HOME\bin > run pkg_svc

UNIX. DEMO_HOME/bin $ ./run.sh pkg_svc

You should find ChatServiceAsync.jar in the DEMO_HOME root directory. Taking a look inside the build file, we see that
this task is called thus:
<target name="pkg_svc"
depends="build_svc"> <echo>Packaging
Service...</echo> <WASPPackager
ServicePackage="ChatServiceAsync"
Jarfile="${basedir}/ChatServiceAsync.jar" Force="true"
classDirectory="${build.dir}" Exclude="ChatServerApp.class" >
<service
class="com.systinet.asyncchat.server.impl.ChatService"
name="ChatServiceAsync" path="/ChatServiceAsync/"/>
</WASPPackager> </target>

In a Windows command line console, you can invoke WaspPackager as follows:

DEMO_HOME\build\server >%WASP_HOME%\bin\WaspPackager -p ChatServiceAsync -o ..\ChatServiceAsync.jar


-n ChatServiceAsync -u /ChatServiceAsync/ -c com.systinet.asyncchat.server.impl.ChatService --classpath . --force

This list provides a summary of the command line switches (ANT attributes) used in these examples.

• -p (@ServicePackage=) - sets the package name

• -o (@Jarfile=)- sets the name of the jar file

• -n (service@name=) - sets the service name

• -u (service@parth=) - sets the endpoint URI for the service

• -c (service@class=) - sets the base class for the service

• --classpath (@classDirectory=) - sets up the path for all included packages, interfaces, and classes.

• --force (@Force=) - tells the packager to overwrite any existing jar file of the same name

Page 7
2.2. Deploying the Service

Looking at ChatSericeAsync.jar, we see that WaspPackager did more than just bundle our application classes into a
standard Java archive. The archive for such a simple service contains the following:

ChatServiceAsync.jar
+-WASP-INF
+-classes
| +-(com.systinet.asyncchat.server.*)
+-wsdl
| +-ChatServiceAsync.wsdl
+-package.xml

Here we see that WaspPackager has generated a default WSDL for the service. You can specify your own with the -
-wsdl-file switch on the command line or the wsdlFile in the ANT task.

WaspPackager has also created a deployment descriptor in the form of package.xml. This generated deployment descriptor
provides only the most rudimentary definitions: the service-endpoint and the service-instance class to which messages
coming to the endpoint should be routed. Through deployment descriptors much more can be defined, such as transport
specifics and access and message security. Custom descriptors can be specified for WaspPackager through the --dd switch
on the command line or the descriptor attribute in ANT.

Until you thoroughly understand Web services and the Systinet Server for Java APIs it's probably safest to rely on the
WSDLs and deployment descriptors generated by default using WaspPackager.

2.2. Deploying the Service


We have a package. Next, we use DeployTool(command-line)/DeployTask(ANT) to load the service into our SSJ Server.

Secure Servers
If you are deploying to an SSJ server that was installed with security features please check the
DEMO_HOME/bin/ant.properties file. The admin.name and admin.pass properties in this file have been set
to the default values of a freshly installed server. If these values have been changed on your server - as they
should have been - please set them here so that ANT can use them. These scripts rely on "HttpBasic" as the se-
curity provider. If this has been disabled in the server then you will need to identify a valid server side security
provider through the "securityProvider" attribute in the Deploy task within the "deploy_svc" target. This can be
found in DEMO_HOME/bin/build.xml.

Windows. DEMO_HOME\bin > run deploy_svc

UNIX. DEMO_HOME/bin $ ./run.sh deploy_svc

If everything in your system is set up correctly you should see the following message in your console display.
deploy_svc:
Deploying Service...
Succesfully deployed at http://localhost:6060

As specified in the package.xml deployment descriptor's service-endpoint@path attribute, the service is now accessible
at http://localhost:6060/ChatServiceAsync/. The WSDL file is accessible at http://localhost:6060/ChatServiceA-
sync/wsdl. Accessing the WSDL URL through HTTP GET with a browser will download the WSDL stored in the archive
generated by WaspPackager.

You can also browse the service through the WASP Administration Console, available at http://localhost:6060/admin/con-
sole. Click on the Web Services node in the left navigation frame. You will see the /ChatServiceAsync/ URI in the
tree. Click this URI to bring up its management page.

Page 8
2.2. Deploying the Service

In our DEMO_HOME/bin/build.xml file the deploy_svc target wraps a call to the Deploy task. This is a simple task re-
quiring two basic attributes or arguments: a target server and a package to be deployed.

<target name="deploy_svc" depends="pkg_svc">


<echo>Deploying Service...</echo>
<Deploy Target="${srv.url}"
jarfile="${basedir}/ChatServiceAsync.jar"
username="${admin.name}"
password="${admin.pass}"
>
</Deploy>
</target>

From a Windows command line the tool version of this task can be called like this:

DEMO_HOME\bin > %WASP_HOME%\bin\Deploy ..\ChatServiceAsync.jar --target http://localhost:6060

The command line arguments and switches (ANT task attributes) have the following sense:

• Arg1 (@jarfile=) - The WaspPackager generated service archive to be deployed.

• --target (@Target=) - the target URL where a Systinet Server for Java DeployService is listening for deployment
request messages.

• --username (@username=) - name used when authenticating with a secure server.

• --password (@passsword=) - password used when authenticating with a secure server.

Undeploy. You may eventually need to undeploy a service. You may have a better version you want to publish or one
you simply want to test. The service can be undeployed through ANT with a call to the UndeployTask in build.xml.
This task is wrapped inside the undeploy_svc target.

Windows. DEMO_HOME\bin > run undeploy_svc

UNIX. DEMO_HOME/bin $ ./run.sh undeploy_svc

The UndeployTask also requires two basic attribute/arguments.


<target name="undeploy_svc">
<echo>Undeploying Service...</echo>
<Undeploy Target="${srv.url}"
Package="http://my.org:ChatServiceAsync" Force="true">
</Undeploy> </target>

Like Deploy this task needs a target URL where an SSJ DeployService is listening. The service to be undeployed is
identified by the QName defined by the Package attribute. If no namespace was provided when the package was generated,
SSJ provides all services with the default namespace http://my.org The remainder of the QName matches the name
assigned to the package through WaspPackager.

Leave the service running for now. We will next begin work on the client side. In this tutorial this is the side that presents
the most new wrinkles.

Page 9
Page 10
Sync and Async Clients
We have the server side up and running. Now, it's time to look at the steps involved in generating clients that will call it.
Instead of making a huge jump and having the asynchronous client talking to our server instantly, let's proceed with
smaller steps. Let's start with a simple synchronous client, that will later evolve into a fully asynchronous beast.

1. The Synchronous Client


Before we can begin coding our client application we need a Java representation of the service that our code can leverage.
This part will be familiar if you have already looked at the first tutorial in this series.

1.1. Generating the Client


As presented in the first tutorial in this series, the WSDL2Java tool can be used for generating our client-side skeletons and
interface definition files. This is available as an ANT task an in our build.xml file is wrapped within the gen_iface
target.

Windows. DEMO_HOME\bin > gen_iface

UNIX. DEMO_HOME/bin $ gen_iface

This will generate the Java sources in the DEMO_HOME/src directory. The call to the task itself looks like this:
<target name="gen_iface" depends="init"
unless="iface.generated"> <WSDL2Java
outputLanguage="java-async" outputDirectory="${client.src.dir}/"
structurePackage="com.systinet.asyncchat.client.iface.struct"
interfacePackage="com.systinet.asyncchat.client.iface"
generateJavaBeans="false" force="true"
wsdlURL="${wsdl.url}"/> </target>

We can achieve the same results with a call to the command line tool. In Windows this call would be:

DEMO_HOME\src > %WASP_HOME%\bin\WSDL2Java --url http://localhost:6060/ChatServiceAsync/wsdl -p


com.systinet.asyncchat.client.iface -s com.systinet.asyncchat.client.iface.struct -l java-async --force

Note that the WSDL2Java tool lets you specify how the code is generated through the outputLanguage attribute or, in the
command line version, the -l flag. On the client side there are three useful settings for this switch:

• java - to generate client side code using synchronous calls only

• java-async - to generate client side code using asynchronous or synchronous calls

• java-impl - generates the service implementation skeleton without async code, you need to flesh out the service side
code.

WSDL2Java can also be used on the server side to generate skeleton code for a Web service conforming to a particular
WSDL. In such a case the language output parameter java-async-server might be useful.

You now have created four Java source files in two new packages:

• com.systinet.asyncchat.client.iface

• ChatService.java

• com.systinet.asyncchat.client.iface.struct.

Page 11
1.2. Running the Client

• ChatMessage.java

• ChatPerson.java

• NoMessageArrivedException.java

The first package contains the interface definition, ChatService.java. The second package contains additional structures
that are used in the interface. An additional file, ChatService.xmap, was generated. This essential resource is used by
SSJ to map between namespaces and objects as they are expressed in messages coming from the service and as they are
actually named in the client context.

If you inspect the generated classes, they should be familiar to you. They are not exact copies of the server side ChatService
interface classes, but they exactly represent its API. The ChatService.java file contains a handful of previously unseen
methods, like beginPostMessage() or endPostMessage(). These methods are used for asynchronous invocation of the
web service. In this step, however, we will focus on the synchronous methods instead.

Now we will make use of this interface and skeleton code in ChatServiceClientSync.java. At a minimum the client should
contain the following core statements.
ChatService service;
String wsdlURI = "http://localhost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";
// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setServiceURL(serviceURI);
service = (ChatService)serviceClient.createProxy(ChatService.class);
// now, call the methods on your Web service interface

This code creates a client side representation of the service description and then a proxy that can be used to call the Web
service remotely. For more complete details, please check the first tutorial in this series (A Ten-Minute Web Service
[http://dev.systinet.com/resources/wasp_jserver/demos/tutorials/ChatService/index].

As we shall see, our current tutorial contains a number of clients. To build the synchronous client, execute the following:

Windows. DEMO_HOME\bin > run build_client_sync

UNIX. DEMO_HOME/bin $ ./run.sh build_client_sync

Like all the build targets in our ANT build file this target just wraps a call to javac.

1.2. Running the Client


To start, let's make the client call our new Web service's methods synchronously while preparing ourselves for a minor
disappointment. To run this initial client:

Windows. DEMO_HOME\bin > run_client_sync

UNIX. DEMO_HOME/bin $ run_client_sync

The run_client_sync target just wraps a standard call to java. If everything has been configured correctly you should
see the following output in the command line console.
DEMO_HOME\bin > run run_client_sync
# Sat Jul 31 14:18:19 CET 2004 Sandman > Enter
Exception in thread "main"
com.systinet.asyncchat.server.iface.NoMessageArrivedE xception:
The time has expired, no new messages have arrived!

Page 12
1.3. Intermezzo - SOAP faults

at com.systinet.asyncchat.server.impl.ChatService.getOrWaitforNewMessage
s(ChatService.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
...

Now what has just happened? The client first sends a message to the server. Then it reads all messages from the server.
In the last step, it waits for new messages to arrive within 10 seconds. As there are no new messages on the server, the
timeout exception is thrown on the server side and transparently transferred and rethrown on the client side. If you manage
to run the client again and run another one within ten seconds, the first one should print the message that the second one
sends instead of throwing an exception.

1.3. Intermezzo - SOAP faults


Before we get to the async client, let's make a short detour into the realm of exception propagation that we've just seen
in action. SOAP includes an abstraction for reporting error and exception states generated on the server side. There's a
special message type in SOAP called a Fault that tells the client that something went wrong on the server side. The most
typical types of SOAP Faults, such as mustUnderstand and versionMismatch are described in the SOAP specification
at www.w3c.org [http://www.w3c.org/TR/2003/REC-soap12-part1-20030624/#soapfault]. Custom SOAP Faults can be
defined in a Web service's WSDL to tell the clients what additional kinds of errors to expect.

Let's return to our sample application. The interface of the service was enhanced with a method getOrWaitforNewMessages
which can throw our custom NoMessageArrivedException. When SSJ deploys an application's service classes as Web
services, it stores information about its methods' exceptions in the WSDL. When a Java method is mapped to a Web service
operation all possible messages that can be sent to and received by the operation must be defined, including exceptions
which get mapped as SOAP Faults.

The following example shows the key elements defining SOAP Fault messaging. This example leaves out the gory XML
Schema details defining how ns0:NoMessageArrivedException gets mapped to com.systinet.asyncchat.serv-
er.iface.NoMessageArrivedException, but trust us, it does.

Example 1. Custom SOAP Fault defined in WSDL


...
<message name="NoMessageArrivedException">
<part name="NoMessageArrivedException"
element="ns0:NoMessageArrivedException" />
</message>
...
<portType name="ChatService">
...
<operation name="getOrWaitforNewMessages" parameterOrder="p0 p1">
<input message="tns:ChatService_getOrWaitforNewMessages__Request_Soap" />
<output message="tns:ChatService_getOrWaitforNewMessages_Response_Soap" />
<fault name="NoMessageArrivedException"
message="tns:NoMessageArrivedException" />
</operation>
...
</portType>

When we create the framework code on the client side, WSDL2Java generates the exception classes. The SSJ runtime
handles deserialization to these classes when they are returned in a SOAP Fault message. Here SSJ on the server-side
generated a SOAP Fault message around a NoMessageArrivedException. As we saw when running the synchronous
client, the exception wrapped in this message will be regenerated by SSJ on the client side.

Page 13
2. Asynchronous API

Example 2. A SOAP Fault inside an HTTP Block


HTTP/1.0 500 Internal Server Error
Date: Mon, 02 Aug 2004 07:20:39 GMT
Connection: close
Server: Systinet WASP Server for Java/5.0 (Java/1.4.1; Windows XP/5.1)
SOAPAction: ""
Content-type: text/xml;charset=UTF-8

<?xml version="1.0" encoding="UTF-8"?>


<e:Envelope xmlns:d="http://www.w3.org/2001/XMLSchema"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wn1="http://idoox.com/interface"
xmlns:wn2="http://systinet.com/wsdl/com/systinet/asyncchat/server/iface/"
xmlns:wn0="http://systinet.com/xsd/SchemaTypes/"
xmlns:e="http://schemas.xmlsoap.org/soap/envelope/">
<e:Body>
<e:Fault xmlns:ns0="http://schemas.xmlsoap.org/wsdl/soap/">
<faultcode>
ns0:Server
</faultcode>
<faultstring>
class com.systinet.asyncchat.server.iface.NoMessageArrivedException:
The time has expired, no new messages have arrived!
</faultstring>
<detail>
<wn0:NoMessageArrivedException
i:type="wn2:NoMessageArrivedException">
<wn2:message i:type="d:string">
The time has expired, no new messages have arrived!
</wn2:message>
</wn0:NoMessageArrivedException>
</detail>
</e:Fault>
</e:Body>
</e:Envelope>

The SOAP specification lets you transfer error states from the service to the client. At runtime, all the SOAP plumbing
is handled by SSJ. When an exception is raised in the service, the SSJ server infrastructure sends back the appropriate
SOAP Fault. The SSJ client infrastructure receives the fault and raises the exception in the client JVM.

Systinet Server for Java's smart SOAP runtime can detect and send standard SOAP Faults as well as map Java runtime
exceptions to and from SOAP faults transparently. You don't have to be concerned about the plumbing at all. All you
need to know is the service interface. Simply be ready to handle exceptions when they come.

2. Asynchronous API
The asynchronous API of any messaging runtime should let the client fire the request, forget about it, do other work and
either be notified when the response arrives or check to see if a response is ready to be pulled from the service or the client
side service-proxy. At this point, we are running into the realm of multithreaded applications with issues such as synchron-
ization. This inevitably leads to more complicated code samples that are more difficult to read. We assume that your
knowledge of Java also covers the essentials of multithreaded programming.

The Systinet Server for Java Asynchronous Invocation API provides developers with two basic patterns of response arrival
processing - callbacks (listeners) and polling. Callbacks are user-registered listeners that are awakened by the framework
when the response comes back from the server. The polling API lets the client periodically check if the response has arrived.

Page 14
2. Asynchronous API

Generally, callbacks are preferred to polling. An application using callbacks can carry on indefinitely, and only needs to
interrupt other processing when the response actually does come. Traditional dirty polling involves putting check messages
on the wire for the server thus adding to network traffic and server load. SSJ's polling is to the client side message queue
and not directly to the server which means that the response may sit there until a process asks for it. Callbacks set client
side processes into action immediately and therefore, if well designed, make a more efficient use of resources than peri-
odic polling. For these reasons we will demonstrate the Callback API in our asynchronous examples.

Before we jump into the updated source code, let's take a look at it in outline. We will initiate the asynchronous invocation
of the service, and register a callback to be invoked when the response is returned. In the meantime, while waiting for the
response to arrive, we'll process other code - for simplicity, a loop that sleeps a couple of seconds, wakes up sending a
new message and then sleeps some more. The loop is broken when the response arrives and the callback handler updates
the application state.

Please note that the usage of the async API doesn't say anything about the way messages are sent or received on the
transport level at this moment. We are going to use an asynchronous API over a synchronous transport for now.

The full listing of the updated client is in ChatServiceClientAPIAsync.java. Here's a snippet of the main method of the
client:

service = (ChatService)serviceClient.createProxy(ChatService.class);
// now, call the methods on your Web Service interface
// let's start with some synchronous calls, print the existing messages
long lastMessageId;
ChatMessage msgs[];
service.postMessage("Enter", "Sandman");
msgs = service.getAllMessages();
lastMessageId = msgs[msgs.length - 1].id;
printMessages(msgs);
// now let's wait for another messages to arrive
System.out.println(">>> Client: Making the asynchronous call, " +
"waiting for response to arrive...");


AsyncConversation async =
service.beginGetOrWaitforNewMessages(lastMessageId, 10000);


MyCallbackClass callback = new MyCallbackClass(service);
async.setCallback(callback);


while (!finishedWork) {
//The client now either sleeps (read: "does other important work");
System.out.println(" Client: ... zzzzZZZ ... " +
"doing other work ... zzZZ ...");
Thread.sleep(2000);
System.out.println(" Client: " +
"Sending another post to the server.");
service.postMessage("Wake Up!", "Sandman");
}
System.out.println("--- Client: All work done, exiting.");

❶ This method call initiates the async invocation. At this moment, the request message is sent to the server in a new
thread, control is returned immediately to the client. The client can now deal with the asynchronous call using the
async object, an instance of the org.systinet.wasp.async.AsyncConversation class.
❷ We create our callback implementation and tell the framework to use this callback when the response for our async
call arrives from the server.

Page 15
2. Asynchronous API

❸ While our request is processed on the server side, our client continues its work. We'll wait in this loop until the response
arrives (see our callback implementation below). In order that our async call doesn't throw the NoMessageArrive-
dException in the service, we send a new message to the server after two seconds, and so the callback should receive
this new message from the chat server.

Here's the callback implementation:


static class MyCallbackClass extends GenericAsyncCallback {

/** proxy to the web service */


ChatService service;

/**
* Constructor that accepts the proxy for the chat service
* that we call asynchronously
* @param service the instance of proxy to the web service
*/
public MyCallbackClass(ChatService service) {
super();
this.service = service;
}

/**
* This is the callback method invoked when the response arrives
* @see org.systinet.wasp.async.AsyncCallback#onResponse(...)
*/
public void onResponse(AsyncConversation async) {
System.out.println(
"<<< Callback: The asynchronous response " +
"from the ChatService has arrived!");


ChatMessage msgs[] = null;
try {
ChatServiceClient.printMessages(
service.endGetOrWaitforNewMessages(async));
} catch (NoMessageArrivedException e) {
System.out.println(" Callback: " +
"The call has timed out!");
} finally {

async.finish();
}
//all work is done now
finishedWork = true;
System.out.println("--- Callback: Leaving!");
}
}

❶ A class declared as a callback handler in the SSJ Asynchronous API should descend from org.systin-
et.wasp.async.GenericAsyncCallback and should implement the org.systinet.wasp.async.AsyncCallback
interface. This interface has two methods that need to be defined: onResponse(org.systinet.wasp.async.Asyn-

Page 16
3. Asynchronous Transport

cConversation) and onTimeout(org.systinet.wasp.async.AsyncConversation). In GenericAsyncCallback,


onResponse() is left abstract.
❷ The method onResponse() gets called whenever a response from the server arrives. First, we read the result from
the service by calling the endGetOrWaitforNewMessages() method on the service proxy. This method either returns
the new messages or rethrows our service's exception.
❸ When the response is processed, we tell the framework not to wait for more messages to come. SSJ is able to process
multiple responses for a single asynchronous call. When finished, we set finishedWork to true letting the main
client thread escape from its loop.

And that's it!

Letting the build target be called by the run target, run the client using ANT as before:

Windows. DEMO_HOME\bin > run_client_async_api

UNIX. DEMO_HOME/bin $ run_client_async_api

Your results should look something like this:


# Thu July 31 19:38:26 EDT 2004 Sandman > Enter
# Thu Sep 18 20:02:06 EDT 2003 Sandman > Enter
>>> Client: Making the asynchronous call,
waiting for response to arrive...
Client: ... zzzzZZZ ... doing other work ... zzZZ ...
Client: Sending another post to the server.
Client: ... zzzzZZZ ... doing other work ... zzZZ ...
<<< Callback: The asynchronous response from
the ChatService has arrived!
# Thu Sep 18 20:02:08 EDT 2003 Sandman > Wake Up!
--- Callback: Leaving!
Client: Sending another post to the server.
--- Client: All work done, exiting.

The actual chat conversation may be different for you, depending on the number of times you have already run your clients.
As you can see, the client sends the async request to the server (the message that starts with >>>). Then, the client does
other interesting work (zzzZZZ) and sends a new message to the server. At that moment, the response is returned from
the server and sent to the callback (the <<< message). At the end, the callback sets finishedWork to true, closes the
conversation and leaves. The client leaves the loop as soon as it gets the chance to do so, and exits.

This client code uses the Asynchronous API, but on the transport level, the call was still synchronous. The HTTP connection
opened by the client remained open all the time until the response was returned. The server has no notion about the client's
asynchronous nature at all in this use case. We'll now introduce one final wrinkle - the Asynchronous Transport API.

3. Asynchronous Transport
We have one last short step before us now. A short one for us developers, a fairly long one for the Web services framework.
The Async API alone didn't make any requirements on the server side. For it, the call was like any other ordinary syn-
chronous call. The asynchronous transport API requires the server to contain a logic that doesn't try to send the response
over the connection opened by the client. Instead, this logic closes the request connection immediately, and opens a new
one to the client once the response message is ready.

This means the client must tell the server to behave differently in order to make a truly asynchronous transport call. Some
metadata must be sent along with the request message, and the server must be able to detect it, read it, and act accordingly.
Systinet Server for Java uses SOAP Headers to pass metadata between parties. The solution is transport independent and
builds on existing industry standards. The header information tells the server the client's address for routing the response
message. It is encoded using WS-Addressing protocols. WS-Addressing is a specification maintained by a number of

Page 17
3. Asynchronous Transport

Web Services vendors (Microsoft, IBM and others). It defines how to use endpoint references and message information
headers. Because of the widespread support of this specification, this solution is interoperable with other frameworks that
support it - like .Net. In the following example we see that WS-Addressing semantics are similar to those of mail routing
protocols.

Example 3. A WS-Addressing SOAP Header


<e:Header>
<wsa:MessageID>uuid:0a52d930-e4a8-11d8-b199-03ab0d51b199</wsa:MessageID>
<wsa:To>http://localhost:6060/ChatServiceAsync/</wsa:To>
<wsa:From>
<wsa:Address>http://myhost.mydomain.com:7070/AsyncCallback_0</wsa:Address>
</wsa:From>
<wsa:ReplyTo>
<wsa:Address>http://myhost.mydomain.com:7070/AsyncCallback_0</wsa:Address>
</wsa:ReplyTo>
<wsa:FaultTo>
<wsa:Address>http://myhost.mydomain.com:7070/AsyncCallback_0</wsa:Address>
</wsa:FaultTo>
<wsa:Action>
http://systinet.com/wsdl/com/systinet/asyncchat/server/impl/ChatService#postMessage?KEx...
</wsa:Action>
</e:Header>

What we really need to do now is to tell the SOAP framework to generate this header information, in short to use asyn-
chronous transports. We do it by adding this line to the client code:
serviceClient.setAsyncTransport("http");

By now you should already be familiar with SSJ's ServiceClient class. The setAsyncTransport(java.lang.String)
method tells SSJ to make all forthcoming calls transport asynchronous. We use "http" as the response message protocol
here because its usage doesn't require any additional configuration. We could make the response come through POP3,
JMS, whatever, but these protocols require a slightly more complex setup.

When using the "http" case, SSJ starts a standalone HTTP server on the client side. The address of this client's server
along with the identification of the invocation (the correlation information) is sent to the service, as we have seen, in the
form of the WS-Addressing SOAP Header. The server reads it, finds out it needs to use an asynchronous response, and
uses this information for sending the response back to the client. Please note that this is a framework matter only. We do
not have to make the smallest change to the existing web service implementation code! The full source of this truly asyn-
chronous client is in ChatServiceClientTransAsync.java. The main process of this application follows:
/**
* @param args the command line arguments
*/
public static void main (String args[]) throws Exception {
ChatService service;
String wsdlURI = "http://localohost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";

// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setServiceURL(serviceURI);

service = (ChatService)serviceClient.createProxy(ChatService.class);
// now, call the methods on your Web service interface
// let's start with some synchronous calls, print the existing messages
long lastMessageId;

Page 18
3. Asynchronous Transport

ChatMessage msgs[];
service.postMessage("Enter", "Sandman");
msgs = service.getAllMessages();
lastMessageId = msgs[msgs.length - 1].id;
printMessages(msgs);
//now let's wait for another messages to arrive
System.out.println(">>> Client: Making the asynchronous call, " + "
waiting for response to arrive...");


serviceClient.setAsyncTransport("http");
//initiate the async invocation;
//read new messages from the server with timeout 10s
AsyncConversation async =
service.beginGetOrWaitforNewMessages(lastMessageId, 10000);
//set up the callback that will be called when the response arrives
MyCallbackClass callback = new MyCallbackClass(service);
async.setCallback(callback);
//the following dumb code demonstrates that the client keeps running
//my callback decides if it's been enough already
while (!finishedWork) {
//The client now either sleeps (read "does other important work");
System.out.println(" Client: ... zzzzZZZ " +
"... doing other work ... zzZZ ...");
Thread.sleep(2000);
System.out.println(" Client: " +
"Sending another post to the server.");
service.postMessage("Wake Up!", "Sandman");
}


Wasp.destroy();
System.out.println("--- Client: All work done, exiting.");
}

❶ Tell the SSJ framework to use transport asynchrony for all forthcoming calls.
❷ As the server was started in the client transparently, we now have to shut it down, otherwise there are leftover threads
running and the client never shuts down completely.

And that's it! We have now implemented a fully asynchronous client. Run the application letting the run target call the
build.

Windows. DEMO_HOME\bin > run_client_async_trans

UNIX. DEMO_HOME/bin $ run_client_async_trans

Please note that the client's internal SSJ server is running on port 7070. If this port is already in use, the client-side server
won't start and the demo won't work. Didn't I say that the differences between clients and servers diminish under asyn-
chronous communication? What a strange-looking term: "client-side server."

If you still yearn for more code, we have a real pièce de résistance ready for you. Be prepared for the final version of the
client with multiple calls and multiple responses. The full source of the client is in ChatServiceClientBeastAsync.java.
Run it with the target run_client_async_beast. Don't blame me if you cannot read the code easily! You've been warned!

Page 19
Page 20
Conclusion
I have attempted to demonstrate that asynchronous invocation in the world of Web services is a reality. With the appro-
priate tools, there's nothing to fear. Standards are already available, and can let us build asynchronous heterogenous
solutions that are mutually interoperable - and more standards are coming soon. In the near future, we will see a rise of
asynchronous messaging, as it is one of the building blocks of the future where documents (not RPC calls) will be the
major transport vehicle for business to business integration.

If you have any questions or comments regarding this tutorial, don't hesitate and contact me. In the next installment, we'll
visit the multi-faceted ground of J2EE integration. Stay tuned!

Page 21
Page 22
Appendix A. Code Samples
A.1. Application Code

Page 23
Example A.1. ChatMessage.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.iface;

/**
*
* This class represents single message, that was sent to the chat server.
*/
public class ChatMessage {

/**
* Date/time of the arrival of the message to the server.
*/
public java.util.Date timestamp;

/**
* unique id of the message; the ids are growing
*/
public long id;

/**
* Author of this message.
*/
public ChatPerson author;
/**
* Text of this message
*/
public String text;

/**
* Default constructor
*/
public ChatMessage() {
super();
}

/**
* Constructor that fills-in all attributes of the class
* @param text the message text
* @param author the author of the message
* @param id the id of the message
* @param timestamp the time of arrival of this message to the server
*/
public ChatMessage(String text,
ChatPerson author,
long id,
java.util.Date timestamp) {
this.text = text;
this.author = author;
this.timestamp = timestamp;
this.id = id;
}
}

Page 24
Example A.2. ChatPerson.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.iface;

/**
*
*This class represents person which sent a message to the chat server.
*/
public class ChatPerson {

public String nickname;

/**
* Default constructor
*/
public ChatPerson() {
super();
}

public ChatPerson(String nickname) {


this.nickname = nickname;
}

Page 25
Example A.3. NoMessageArrivedException.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.iface;

/**
* This is a demonstration of an application exception that is transmitted from the service
* to the client via SOAP as a SOAP Fault
*/
public class NoMessageArrivedException extends Exception {

/**
* The default constructor
*/
public NoMessageArrivedException() {
}

/**
* This constructor accepts the error message as its parameter
* @param message the error message of the exception
*/
public NoMessageArrivedException(String message) {
super(message);
}

Page 26
Example A.4. IChatService.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.iface;

/**
*
* This is the programmatic interface of our chat server application
*/

public interface IChatService {

/**
* This constant defines id of last message when there's no message.
*/
long NO_MESSAGE = -1;

/**
* This constant defines max available timeout
*/
long DEFAULT_TIMEOUT = -1;

/**
* post new messages to the chat server using this method
* @param message the content of the message
* @param nickname the nick name of the posting person
*/
public long postMessage(String message, String nickname);

/**
* Gets all messages sent to the server so far.
* @return array of all messages. When there are no messages, an empty array is
* returned. The null value is never returned.
*/
ChatMessage[] getAllMessages();

/**
* Returns all messages sent after a message with a specified id.
* @param id the idp, (message with this id is excluded from the returned
* array). NO_MESSAGE (-1) means get all messages
* @return array of all messages that fulfill the condition, when no message is found,
* an empty array is returned. The null value is never returned.
*/
ChatMessage[] getMessagesAfter(long precedingMessageId);

/**
* Gets timestamp of the last message recieved by the server.
* @return the id of the last message, or NO_MESSAGE (-1) when there's no message
* at the server yet.
*/
long getLastMessageId();

/**
* This method is a wait'n'block variant of the getMessagesAfter method. It waits until at
* least single new message arrives, but only for a specified amount of time.
* When the wait time is exceeded, an exception is thrown

Page 27
* @param afterId get messages newer than the one with this Id
* @param waitMillis the amount of time for waiting in milliseconds, DEFAULT_TIMEOUT (-1)
* for the default value of the implementation
*/

ChatMessage[] getOrWaitforNewMessages(long afterId, long waitMillis)


throws NoMessageArrivedException;

Page 28
Example A.5. ChatService.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.impl;

import com.systinet.asyncchat.server.iface.ChatMessage;
import com.systinet.asyncchat.server.iface.IChatService;
import com.systinet.asyncchat.server.iface.NoMessageArrivedException;

/**
* This is the implementation of the Web Service implementing the
* @see com.systinet.simplechat.server.iface.IChatService interface.
*/
public class ChatService implements IChatService {

/**
* all calls to service are delegated to this backend implementation
* following the Adaptor pattern
*/
private SimpleBackendImpl backendImpl;

public ChatService() {
backendImpl = SimpleBackendImpl.getInstance();
}

/**
* This method just calls the backend implementation
*/
public long postMessage(String message, String nickname) {
return backendImpl.postMessage(message, nickname);
}

/**
* This method just calls the backend implementation
*/
public ChatMessage[] getAllMessages() {
return backendImpl.getAllMessages();
}

/**
* This method just calls the backend implementation
*/
public ChatMessage[] getMessagesAfter(long precedingMessageId) {
return backendImpl.getMessagesAfter(precedingMessageId);
}

/**
* This method just calls the backend implementation
*/
public long getLastMessageId() {
return backendImpl.getLastMessageId();
}

/**
* This method calls the backend implementation and throws an exception

Page 29
* when there's a timeout
*/
public ChatMessage[] getOrWaitforNewMessages(long precedingMessageId,
long timeout) throws NoMessageArrivedException {
ChatMessage msgs[] = backendImpl.getMessagesAfter(precedingMessageId,
timeout);
if(msgs == null) throw new NoMessageArrivedException(
"The time has expired, no new messages have arrived!");
return msgs;
}

Page 30
Example A.6. SimpleBackendImpl.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.server.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.systinet.asyncchat.server.iface.ChatMessage;
import com.systinet.asyncchat.server.iface.ChatPerson;

/**
* This is the simplest backend implementation of the chat server, but it servers well
* for our demo.
* The backend logic can be implemented in many ways, depending on requirements on
* performance, scalability, persistence - eg. using DB, EJBs etc.
*/
public class SimpleBackendImpl {

/** Singleton instance */


private static final SimpleBackendImpl _instance = new SimpleBackendImpl();
/** Store for messages */
private List messages;
/** Pointer to the last message entered */
private ChatMessage lastMessage = null;
/** Id counter */
private long lastid = 0;
/** The maximum timeout for methods that wait */
private long defaultTimeout = 20000;

/**
* Default constructor
*/
public SimpleBackendImpl() {
super();
messages = new ArrayList(100);
}

/**
* Singleton instance getter
* This method returns the unique instance
* @return the unique instance
*/
public static final SimpleBackendImpl getInstance() {
return _instance;
}

/**
* The backend implementation of the postMessage method
*/
synchronized public long postMessage(String message, String nickname) {
if (message == null)
throw new IllegalArgumentException(
"The message argument to the postMessage method may not be null.");
if (nickname == null)

Page 31
throw new IllegalArgumentException(
"The nickname argument to the postMessage method may not be null.");
ChatPerson author = new ChatPerson();
long id;
synchronized (this) {
//generate unique id
id = ++lastid;
}
lastMessage =
new ChatMessage(message, new ChatPerson(nickname), id, new Date());
messages.add(lastMessage);
//notify all waiting threads (if any) that a new message has arrived
notifyAll();
return id;
}

/**
* The backend implementation of the getAllMessages method
*/
public ChatMessage[] getAllMessages() {
return (ChatMessage[]) messages.toArray(
new ChatMessage[messages.size()]);
}

/**
* The backend implementation of the getMessagesAfter method
*/
public ChatMessage[] getMessagesAfter(long id) {
if (id < -1)
throw new IllegalArgumentException(
"The id argument to the getChatItemsAfter method may not be negative.");
//check if there are any newer messages
if (lastMessage != null && lastMessage.id > id) {
// now get the list of all newer messages
// this is the simplest and slowest method, to speed up,
// implement the interval division method
int i;
int j = messages.size();
for (i = 0; i < j; i++) {
if (((ChatMessage) messages.get(i)).id > id) {
//performance hack
if (i == 0)
return getAllMessages();
//allocate the new array and copy the values manually
int length = j - i;
ChatMessage msgs[] = new ChatMessage[length];
for (j = 0; j < length; j++) {
msgs[j] = (ChatMessage) messages.get(i + j);
}
return msgs;
}
}
//when no newer messages found, return an empty array
}
return new ChatMessage[0];
}

/**
* The backend implementation of the getLastMessageId method

Page 32
*/
public long getLastMessageId() {
if (lastMessage == null)
return -1;
return lastMessage.id;
}

/**
* The backend implementation of the getMessagesAfterWait method
* @return the array of new messages or null when nothing new has arrived
*/
public ChatMessage[] getMessagesAfter(long id, long timeout) {
if (id < -1)
throw new IllegalArgumentException(
"The id argument to the getChatItemsAfter method may not be negative.");
//check if there are any newer messages
if (lastMessage != null && lastMessage.id > id) {
return getMessagesAfter(id);
}
//when no newer messages found, wait for the new message, or timeout
//it's no rocket science, let's make it as simple as possible
if (timeout < 0)
timeout = getDefaultTimeout();
try {
//wait for a message to arrive, or timeout
synchronized (this) {
wait(timeout);
}
} catch (Exception e) {
//we probably got interrupted, this error handling is bad but
//still better than doing nothing
e.printStackTrace();
} finally {
//if anything has arrived, return it
if (lastMessage != null && lastMessage.id > id) {
return getMessagesAfter(id);
} else {
//nothing to return
}
}
return null;
}

/**
* gets the maximum timeout
* @return thr maximum timeout in millis
*/
public long getDefaultTimeout() {
return defaultTimeout;
}

/**
* sets the maximum timeout
* @param maxTimeout the new maxim timeout in millis
*/
public void setDefaultTimeout(long defaultTimeout) {
this.defaultTimeout = defaultTimeout;
}
}

Page 33
A.2. Service Code
Example A.7. ChatServerApp.java
// Copyright 2004 Systinet Corp. All rights reserved.
// Use is subject to license terms.

package com.systinet.asyncchat.server.impl;

import org.systinet.wasp.*;
import org.systinet.wasp.webservice.Registry;

/**
* This is the Chat Server Version 2 Application, that runs WASP Server and
* registers the ChatService.
*/

public class ChatServerApp {

public static void main(String[] args) throws Exception {


Wasp.startServer("http://localhost:6060");
Registry.publish("/ChatServiceAsync/", ChatService.class);
System.out.println("Chat server V2 is up an running.");
}
}

Page 34
A.3. Client Code

Page 35
Example A.8. Generated ChatService.java

package com.systinet.asyncchat.client.iface;

/**

*/
public interface ChatService {

/**

*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] getAllMessages();

/**
* Asynchronous 'begin' version of operation getAllMessages
*/
org.systinet.wasp.async.AsyncConversation beginGetAllMessages();

/**
* Asynchronous 'end' version of operation getAllMessages
*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] endGetAllMessages(
org.systinet.wasp.async.AsyncConversation conversation);

/**

*/
long getLastMessageId();

/**
* Asynchronous 'begin' version of operation getLastMessageId
*/
org.systinet.wasp.async.AsyncConversation beginGetLastMessageId();

/**
* Asynchronous 'end' version of operation getLastMessageId
*/
long endGetLastMessageId(org.systinet.wasp.async.AsyncConversation conversation);

/**

*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] getMessagesAfter(
long precedingMessageId);

/**
* Asynchronous 'begin' version of operation getMessagesAfter
*/
org.systinet.wasp.async.AsyncConversation beginGetMessagesAfter(
long precedingMessageId);

Page 36
/**
* Asynchronous 'end' version of operation getMessagesAfter
*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] endGetMessagesAfter(
org.systinet.wasp.async.AsyncConversation conversation);

/**

*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] getOrWaitforNewMessages(
long precedingMessageId, long timeout)
throws com.systinet.asyncchat.client.iface.struct.NoMessageArrivedException;

/**
* Asynchronous 'begin' version of operation getOrWaitforNewMessages
*/
org.systinet.wasp.async.AsyncConversation beginGetOrWaitforNewMessages(
long precedingMessageId, long timeout);

/**
* Asynchronous 'end' version of operation getOrWaitforNewMessages
*/
com.systinet.asyncchat.client.iface.struct.ChatMessage[] endGetOrWaitforNewMessages(
org.systinet.wasp.async.AsyncConversation conversation)
throws com.systinet.asyncchat.client.iface.struct.NoMessageArrivedException;

/**

*/
long postMessage(java.lang.String message, java.lang.String nickname);

/**
* Asynchronous 'begin' version of operation postMessage
*/
org.systinet.wasp.async.AsyncConversation beginPostMessage(java.lang.String message,
java.lang.String nickname);

/**
* Asynchronous 'end' version of operation postMessage
*/
long endPostMessage(org.systinet.wasp.async.AsyncConversation conversation);

/*
* Generated by Systinet WSDL2Java
* http://www.systinet.com
*/

Page 37
Example A.9. Generated ChatMessage.java

package com.systinet.asyncchat.client.iface.struct;

/**
*
*/
public class ChatMessage {

public com.systinet.asyncchat.client.iface.struct.ChatPerson author;


public long id;
public java.lang.String text;
public java.util.Date timestamp;

/*
* Generated by Systinet WSDL2Java
* http://www.systinet.com
*/

Example A.10. Generated ChatPerson.java

package com.systinet.asyncchat.client.iface.struct;

/**
*
*/
public class ChatPerson {

public java.lang.String nickname;

/*
* Generated by Systinet WSDL2Java
* http://www.systinet.com
*/

Page 38
Example A.11. Generated NoMessageArrivedException.java

package com.systinet.asyncchat.client.iface.struct;

/**
*
*/
public class NoMessageArrivedException extends java.lang.Exception {

public NoMessageArrivedException(java.lang.String message) {


super(message);

/*
* Generated by Systinet WSDL2Java
* http://www.systinet.com
*/

Page 39
Example A.12. ChatServiceClientSync.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.client;

import javax.xml.namespace.QName;
import org.systinet.wasp.webservice.ServiceClient;

import com.systinet.asyncchat.client.iface.ChatService;
import com.systinet.asyncchat.client.iface.struct.ChatMessage;
import com.systinet.asyncchat.client.iface.struct.ChatPerson;

public class ChatServiceClientSync extends Object {

/**
* @param args the command line arguments
*/
public static void main(String args[]) throws Exception {
ChatService service;
String wsdlURI = "http://localhost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";

// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setServiceURL(serviceURI);

service = (ChatService) serviceClient.createProxy(ChatService.class);

// now, call the methods on your Web Service interface


//eg. service.getMessage();
long lastMessageId = service.postMessage("Enter", "Sandman");
printMessages(service.getOrWaitforNewMessages(-1, 10000));
printMessages(service.getOrWaitforNewMessages(lastMessageId, 10000));
}

/**
* Utility method, prints out the messages to stdout.
* @param msgs the messages to print.
*/
public static void printMessages(ChatMessage msgs[]) {
if (msgs.length == 0) {
System.out.println("no messages to print");
} else {
for (int i = 0; i < msgs.length; i++) {
printMessage(msgs[i]);
}
}
}

/**
* Utility method, prints single message to stdout.
* @param msg the message to print
*/
private static void printMessage(ChatMessage msg) {
System.out.println(

Page 40
(new StringBuffer("# ").append(msg.timestamp.toString()))
.append(" ")
.append(msg.author.nickname)
.append(" > ")
.append(msg.text));
}

Page 41
Example A.13. ChatServiceClientAPIAsync.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.client;

import org.systinet.wasp.async.AsyncConversation;
import org.systinet.wasp.async.GenericAsyncCallback;
import org.systinet.wasp.webservice.ServiceClient;

import com.systinet.asyncchat.client.iface.ChatService;
import com.systinet.asyncchat.client.iface.struct.ChatMessage;
import com.systinet.asyncchat.client.iface.struct.NoMessageArrivedException;

public class ChatServiceClientAPIAsync extends Object {

/** This boolean shows if the async call has been finished already */
static boolean finishedWork = false;

/**
* @param args the command line arguments
*/
public static void main(String args[]) throws Exception {
ChatService service;
String wsdlURI = "http://localhost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";

if(System.getProperty("wsdl.url") != null){
wsdlURI = System.getProperty("wsdl.url");
}

if(System.getProperty("ws.url") != null){
serviceURI = System.getProperty("ws.url");
}

// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setServiceURL(serviceURI);

service = (ChatService) serviceClient.createProxy(ChatService.class);


// now, call the methods on your Web Service interface
// let's start with some synchronous calls, print the existing messages
long lastMessageId;
ChatMessage msgs[];
service.postMessage("Enter", "Sandman");
msgs = service.getAllMessages();
lastMessageId = msgs[msgs.length - 1].id;
printMessages(msgs);
//now let's wait for another messages to arrive
System.out.println(
">>> Client: Making the asynchronous call, waiting for response to arrive...");
//initiate the async invocation; read new messages from the server with timeout 10s
AsyncConversation async =
service.beginGetOrWaitforNewMessages(lastMessageId, 10000);
//setup the callback that will be called when the response arrives

Page 42
MyCallbackClass callback = new MyCallbackClass(service);
async.setCallback(callback);
//the following dumb code demonstrates that the client keeps running
//my callback decides if it's been enough already
while (!finishedWork) {
//The client now either sleeps (read "does other important work");
System.out.println(
" Client: ... zzzzZZZ ... doing other work ... zzZZ ...");
Thread.sleep(2000);
System.out.println(
" Client: Sending another post to the server.");
service.postMessage("Wake Up!", "Sandman");
}
System.out.println("--- Client: All work done, exiting.");
}

static class MyCallbackClass extends GenericAsyncCallback {

/** proxy to the web service */


ChatService service;

/**
* Contructor that accepts the proxy for the chat service that we call asynchronously
* @param service the instance of proxy to the web service
*/
public MyCallbackClass(ChatService service) {
super();
this.service = service;
}

/**
* This is the callback method invoked when the response arrives
*/
public void onResponse(AsyncConversation async) {
System.out.println(
"<<< Callback: The asynchronous response from the ChatService has arrived!");
//get the data from the async response
ChatMessage msgs[] = null;
try {
ChatServiceClient.printMessages(
service.endGetOrWaitforNewMessages(async));
} catch (NoMessageArrivedException e) {
System.out.println(" Callback: The call has timed out!");
} finally {
//we are finished with the call
async.finish();
}
//all work is done now
finishedWork = true;
System.out.println("--- Callback: Leaving!");
}
}

/**
* Utility method, prints out the messages to stdout.
* @param msgs the messages to print.
*/
public static void printMessages(ChatMessage msgs[]) {
if (msgs.length == 0) {

Page 43
System.out.println("no messages to print");
} else {
for (int i = 0; i < msgs.length; i++) {
printMessage(msgs[i]);
}
}
}

/**
* Utility method, prints single message to stdout.
* @param msg the message to print
*/
private static void printMessage(ChatMessage msg) {
System.out.println(
(new StringBuffer("# ").append(msg.timestamp.toString()))
.append(" ")
.append(msg.author.nickname)
.append(" > ")
.append(msg.text));
}

Page 44
Example A.14. ChatServiceClientTransAsync.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.client;

import org.systinet.wasp.Wasp;
import org.systinet.wasp.async.AsyncConversation;
import org.systinet.wasp.async.GenericAsyncCallback;
import org.systinet.wasp.webservice.ServiceClient;

import com.systinet.asyncchat.client.iface.ChatService;
import com.systinet.asyncchat.client.iface.struct.ChatMessage;
import com.systinet.asyncchat.client.iface.struct.NoMessageArrivedException;

public class ChatServiceClientTransAsync extends Object {

/** This boolean shows if the async call has been finished already */
static boolean finishedWork = false;

/**
* @param args the command line arguments
*/
public static void main(String args[]) throws Exception {
ChatService service;
String wsdlURI = "http://localhost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";

if(System.getProperty("wsdl.url") != null){
wsdlURI = System.getProperty("wsdl.url");
}

if(System.getProperty("ws.url") != null){
serviceURI = System.getProperty("ws.url");
}

// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setServiceURL(serviceURI);

service = (ChatService) serviceClient.createProxy(ChatService.class);


// now, call the methods on your Web Service interface
// let's start with some synchronous calls, print the existing messages
long lastMessageId;
ChatMessage msgs[];
service.postMessage("Enter", "Sandman");
msgs = service.getAllMessages();
lastMessageId = msgs[msgs.length - 1].id;
printMessages(msgs);
//now let's wait for another messages to arrive
System.out.println(
">>> Client: Making the asynchronous call, waiting for response to arrive...");
//setup the transport asynchrony
serviceClient.setAsyncTransport("http");
//initiate the async invocation; read new messages from the server with timeout 10s
AsyncConversation async =

Page 45
service.beginGetOrWaitforNewMessages(lastMessageId, 10000);
//setup the callback that will be called when the response arrives
MyCallbackClass callback = new MyCallbackClass(service);
async.setCallback(callback);
//the following dumb code demonstrates that the client keeps running
//my callback decides if it's been enough already
while (!finishedWork) {
//The client now either sleeps (read "does other important work");
System.out.println(
" Client: ... zzzzZZZ ... doing other work ... zzZZ ...");
Thread.sleep(2000);
System.out.println(
" Client: Sending another post to the server.");
service.postMessage("Wake Up!", "Sandman");
}
//clean up, release all resources, stop the http server
Wasp.destroy();
System.out.println("--- Client: All work done, exiting.");
}

static class MyCallbackClass extends GenericAsyncCallback {

/** proxy to the web service */


ChatService service;

/**
* Contructor that accepts the proxy for the chat service that we call asynchronously
* @param service the instance of proxy to the web service
*/
public MyCallbackClass(ChatService service) {
super();
this.service = service;
}

/**
* This is the callback method invoked when the response arrives
*/
public void onResponse(AsyncConversation async) {
System.out.println(
"<<< Callback: The asynchronous response from the ChatService has arrived!");
//get the data from the async response
ChatMessage msgs[] = null;
try {
ChatServiceClient.printMessages(
service.endGetOrWaitforNewMessages(async));
} catch (NoMessageArrivedException e) {
System.out.println(" Callback: The call has timed out!");
} finally {
//we are finished with the call
async.finish();
}
//all work is done now
finishedWork = true;
System.out.println("--- Callback: Leaving!");
}
}

/**
* Utility method, prints out the messages to stdout.

Page 46
* @param msgs the messages to print.
*/
public static void printMessages(ChatMessage msgs[]) {
if (msgs.length == 0) {
System.out.println("no messages to print");
} else {
for (int i = 0; i < msgs.length; i++) {
printMessage(msgs[i]);
}
}
}

/**
* Utility method, prints single message to stdout.
* @param msg the message to print
*/
private static void printMessage(ChatMessage msg) {
System.out.println(
(new StringBuffer("# ").append(msg.timestamp.toString()))
.append(" ")
.append(msg.author.nickname)
.append(" > ")
.append(msg.text));
}

Page 47
Example A.15. ChatServiceClientBeastAsync.java

// Copyright 2004 Systinet Corp. All rights reserved.


// Use is subject to license terms.

package com.systinet.asyncchat.client;

import org.systinet.wasp.Wasp;
import org.systinet.wasp.async.AsyncConversation;
import org.systinet.wasp.async.GenericAsyncCallback;
import org.systinet.wasp.webservice.ServiceClient;

import com.systinet.asyncchat.client.iface.ChatService;
import com.systinet.asyncchat.client.iface.struct.ChatMessage;
import com.systinet.asyncchat.client.iface.struct.NoMessageArrivedException;

public class ChatServiceClientBeastAsync extends Object {

public static void main(String args[]) throws Exception {


ChatService service;
String wsdlURI = "http://localhost:6060/ChatServiceAsync/wsdl";
String serviceURI = "http://localhost:6060/ChatServiceAsync/";

if(System.getProperty("wsdl.url") != null){
wsdlURI = System.getProperty("wsdl.url");
}

if(System.getProperty("ws.url") != null){
serviceURI = System.getProperty("ws.url");
}

// lookup service
ServiceClient serviceClient = ServiceClient.create(wsdlURI);
serviceClient.setAsyncTransport("http");
serviceClient.setServiceURL(serviceURI);

service = (ChatService) serviceClient.createProxy(ChatService.class);


System.out.println(
">>> Client: Making the call, waiting for response to come...");
//initite the async invocation; read all messages from the server with maximum timeout
AsyncConversation async = service.beginGetOrWaitforNewMessages(-1, -1);
//setup the callback that will be called when the response arrives
MyCallbackClass callback = new MyCallbackClass(service);
async.setCallback(callback);
//the following dumb code demonstrates that the client keeps running
//my callback decides if it's been enough already
while (callback.hasMoreToDo()) {
if (Math.random() < 0.66) {
//The client now either sleeps (read "does other important work");
System.out.println(
" Client: ... zzzzZZZ ... doing other work ... zzZZ ...");
Thread.sleep(5000);
} else {
//or sends a new message to the chat server.
System.out.println(
" Client: Posting a new message to the chat server!");
service.postMessage("Wake up!", "Sandman");

Page 48
}
}
//clean up, release all resources, stop the http server
Wasp.destroy();
System.out.println("--- Client: All work done, exiting.");
}

static class MyCallbackClass extends GenericAsyncCallback {

/** proxy to the web service */


ChatService service;

/** counter of responses to be read before the callback ends */


int responsesToRead = 3;

/**
* Contructor that accepts the proxy for the chat service that we call asynchronously
* @param service the instance of proxy to the web service
*/
public MyCallbackClass(ChatService service) {
super();
this.service = service;
}

/**
* This is the callback method invoked when the response arrives
*/
public void onResponse(AsyncConversation async) {
System.out.println(
"<<< Callback: The asynchronous response from the ChatService has arrived!");
//get the data from the async response
ChatMessage msgs[] = null;
try {
msgs = service.endGetOrWaitforNewMessages(async);
ChatServiceClient.printMessages(msgs);
} catch (NoMessageArrivedException e) {
System.out.println(" Callback: The call has timed out!");
} finally {
//we are finished with the call
async.finish();
}
responsesToRead--;
if (hasMoreToDo()) { //are there more messages to process?
//get the id of the last message retrieved; or -1 when there's no messages arrived yet
long last = -1;
if (msgs != null && msgs.length > 0)
last = msgs[msgs.length - 1].id;
System.out.println(
">>> Callback: ... doing another call, now waiting for another response ...");
//start another async call, get all messages since the last time
async = service.beginGetOrWaitforNewMessages(last, -1);
//be the callback for this call
async.setCallback(this);
} else {
//all work is done now
System.out.println("--- Callback: Leaving!");
}
}

Page 49
/**
* @return true when there's more to do
*/
public boolean hasMoreToDo() {
return responsesToRead > 0;
}

/**
* Utility method, prints out the messages to stdout.
* @param msgs the messages to print.
*/
public static void printMessages(ChatMessage msgs[]) {
if (msgs.length == 0) {
System.out.println("no messages to print");
} else {
for (int i = 0; i < msgs.length; i++) {
printMessage(msgs[i]);
}
}
}

/**
* Utility method, prints single message to stdout.
* @param msg the message to print
*/
private static void printMessage(ChatMessage msg) {
System.out.println(
(new StringBuffer("# ").append(msg.timestamp.toString()))
.append(" ")
.append(msg.author.nickname)
.append(" > ")
.append(msg.text));
}
}

Page 50
Appendix B. Legal
B.1. Copyright
This document and the information contained herein are the property of Systinet Corporation and shall not be reproduced
or copied in whole or in part without written permission of Systinet Corp.

Copyright ' 2003-2004 Systinet Corp. All Rights Reserved.

B.2. Disclaimer
The information in this document is preliminary and is subject to change without notice and should not be construed as
a commitment by Systinet Corporation.

SYSTINET CORPORATION SHALL HAVE NO LIABILITY FOR THIS DOCUMENT, INCLUDING ANY LIABILITY
FOR NEGLIGENCE. SYSTINET CORPORATION MAKES NO WARRANTIES, EXPRESS, IMPLIED, STATUTORY,
OR IN ANY OTHER COMMUNICATION. SYSTINET CORPORATION SPECIFICALLY DISCLAIMS ANY
WARRANTY OF MERCHANTABILITY OR SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE,
TITLE AND NON-INFRINGEMENT.

B.3. Trademarks
• Systinet, WASP, "The Web Services Infrastructure Company" and "Web Services That Work" are trademarks of
Systinet Corp.

• Java and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and
other countries.

• Microsoft, Windows and Windows NT and the Windows logo are trademarks or registered trademarks of Microsoft
Corporation in the United States and other countries.

• UNIX is a registered trademark of The Open Group in the United States and other countries.

• Other company, product, and service names mentioned in these documents may be trademarks or service marks of
others.

Page 51

You might also like