2007/04/18
18 Apr, 2007

Using Sandesha2 for Reliable Two-way WS Communication

  • Chamikara Jayalath
  • Software Engineer - WSO2

Introduction

Apache Axis2 provides good support for doing two-way message interactions. The main utility you can use is the ServiceClient class which provides following methods.

OMElement sendReceive(OMElement elem);
OMElement sendReceive(QName operationQName, OMElement xmlPayload);
void sendReceiveNonBlocking(OMElement elem, Callback callback);
void sendReceiveNonBlocking(QName operation, OMElement elem, Callback callback);

When writing your client code you can choose the way the underneath transport channel (for e.g. HTTP) should behave. Axis2 can be configured to use transport channels in one of the following ways.

  1. Using two different transport channels for request and response messages.
  2. Using the same transport channel for both the request and the response messages.

As long as you are only using Axis2 things are simple. But as your requirements grow you may consider using Apache Sandesha2 underneath, to do this message exchange reliably. This will allow you to communicate with Web services in a guaranteed manner.

According to the WSRM specification [1] ,Apache Sandesha2 sends several control messages back and forth during the application message exchange. In order to configure these control messages as per your reliability requirements, you need to be aware of certain additional configurations. Following sections of this tutorial describes those configurations in detail.

Using Two Different Transport Channels

This is the recommended approach if your are using Apache Sandesha2 [2] underneath to send your messages reliably. In this approach the response will come to a listener started by the client side and it is assumed that a significant delay in receiving the response is acceptable.

In bad network conditions it will not be possible to do a fair approximation of the time it will take for the response to reach the client side. In such scenarios it may be a waste of resources to keep a thread waiting to get the response message in the same channel.

So the recommended way to write your client code is given below. Here you will be using the ServiceClient.sendReceiveNonBlocking method. The response will be delivered to a callback object, thus you do not have to make any assumptions on the time at which the response will appear.

private void runClient () throws Exception {

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(AXIS2_REPO,AXIS2_XML);
ServiceClient serviceClient = new ServiceClient (configContext,null);
Options clientOptions = new Options ();
serviceClient.setOptions(clientOptions);

clientOptions.setTo(new EndpointReference (toEPR));
clientOptions.setAction("urn:wsrm:EchoString");
serviceClient.engageModule(new QName ("sandesha2"));
clientOptions.setUseSeparateListener(true);

serviceClient.sendReceiveNonBlocking (getEchoOMBlock("echo1","key1"),
new TestCallback ("Callback 1"));
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo2","key2"),
new TestCallback ("Callback 2"));

clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo5","key1"),
new TestCallback ("Callback 3"));

//Here you have to wait till sandesha2 finishes the work, one options is to
//use Sandesha2 reports here.

configContext.getListenerManager().stop();
serviceClient.cleanup();
}

Also some users may need to do two channel blocking invocation (I.e., they want to call the ServiceClient.sendReceive() method). If you are using Sandesha2 underneath you must make sure that you have set a larger waiting time to the Options object. Otherwise the ServiceClient object may release the block before all the WSRM control messages get exchanged.

The client code will look like following:

 

private void runClient () throws Exception {

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(AXIS2_REPO,AXIS2_XML);
ServiceClient serviceClient = new ServiceClient (configContext,null);
Options clientOptions = new Options ();
serviceClient.setOptions(clientOptions);

clientOptions.setTo(new EndpointReference (toEPR));
clientOptions.setAction("urn:wsrm:EchoString");
serviceClient.engageModule(new QName ("sandesha2"));
clientOptions.setUseSeparateListener(true);
clientOptions.setTimeOutInMilliSeconds(10000);
OMElement result = serviceClient.sendReceive (getEchoOMBlock("echo1","key1"));
printResult (result);
result = serviceClient.sendReceive(getEchoOMBlock("echo2","key2"));
printResult (result);

clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
result = serviceClient.sendReceive(getEchoOMBlock("echo5","key1"));
printResult (result);

configContext.getListenerManager().stop();
serviceClient.cleanup();
}

Using the Same Transport to do InOut Message Exchanges.

Even though the approach mentioned above is recommended when using Sandesha2, some users may not be able to adhere to that. The main reason is it's need to start a listener at the client side. Here are several scenarios where the client will not be able to do this.

  • When the client is behind a firewall the endpoint will not be visible to the outside.
  • The client may be running within a container which does not allow a listener to be started.

In such situations there is the need to get the response messages without starting an endpoint at the client side. If you are using a transport that support this (e.g. HTTP) you can configure Sandesha2 to do this. There are several configuration options you have to choose here and they depend on the WSRM version you are working with.

For WSRM 1.0 :

When you are working with WSRM 1.0 [1] Apache Sandesha2 retransmits application messages to get the reliable response. I.e., a certain application message will be retransmitted till both the acknowledgement and the response message for it are received through the back channel (In normal operation application messages are retransmitted only till a valid acknowledgement is received for it).

The TerminateSequence of the response side will be delivered in the back channel of the request side TerminateSequence message.

Here are the guidelines you should follow to do anonymous InOut message exchanges in WSRM 1.0.

  1. Write your client code as you would normally do and set the replyTo value to the WS-Addressing anonymous URI.
  2. Make sure that you've offered a sequenceID for the response channel.

The completed client code will be similar to the following. Important lines have been highlighted.

 

private void runClient () throws Exception {

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(AXIS2_REPO,AXIS2_XML);
ServiceClient serviceClient = new ServiceClient (configContext,null);
Options clientOptions = new Options ();
serviceClient.setOptions(clientOptions);

serviceClient.engageModule(new QName ("sandesha2"));
clientOptions.setTo(new EndpointReference (toAddress));
clientOptions.setAction("urn:wsrm:EchoString");


clientOptions.setProperty(SandeshaClientConstants.OFFERED_SEQUENCE_ID,
SandeshaUtil.getUUID());


serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo1","sequence1"),
new TestCallback ("Callback 1"));
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo2","sequence1"),
new TestCallback ("Callback 2"));

clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo3","sequence1"),
new TestCallback ("Callback 3"));

//Here you have to wait till sandesha2 finishes the work, one options is to
//use Sandesha2 reports here.
}

For WSRM 1.1 :

WSRM 1.1 introduces a new mechanism called MakeConnection to get the response messages without starting a listener at the client side. The idea is basically to send a special control message called MakeConnection to get the reply in it's back channel. The specification introduces two methods to use MakeConnection message and Sandesha2 supports both of them.

Sandesha2 policies offer some configurations options to allow you to customize this MakeConnection message exchange. The part of the Sandesha2 policies that provide these configuration options is given below.

<sandesha2:MakeConnection>
<sandesha2:Enabled>true</sandesha2:Enabled>
<sandesha2:UseRMAnonURI>true</sandesha2:UseRMAnonURI>
</sandesha2:MakeConnection>

Now lets look at the two approaches you can take when using the MakeConnection based approach.

Method 1:

In this method your client will be offering a sequenceID for the back channel. Performance wise this approach may be better since the server does not have to do a separate CreateSequence message exchange for the back channel. You have to set the offered sequenceID from the client code.

In the policy configuration set the MakeConnection/Enabled policy to true. Also it is recommended to set the UseRMAnonURI policy to false. (In Axis2/Axis2 scenario the code will work even when this set to true).

A sample code block is given below:

 

private void runClient () throws Exception {

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(AXIS2_REPO,AXIS2_XML);
ServiceClient serviceClient = new ServiceClient (configContext,null);
Options clientOptions = new Options ();
serviceClient.setOptions(clientOptions);

serviceClient.engageModule(new QName ("sandesha2"));
clientOptions.setTo(new EndpointReference (toAddress));
clientOptions.setAction("urn:wsrm:EchoString");

clientOptions.setProperty(SandeshaClientConstants.RM_SPEC_VERSION,
Sandesha2Constants.SPEC_VERSIONS.v1_1);



clientOptions.setProperty(SandeshaClientConstants.OFFERED_SEQUENCE_ID,
SandeshaUtil.getUUID());


serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo1","sequence1"),
new TestCallback ("Callback 1"));
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo2","sequence1"),
new TestCallback ("Callback 2"));

clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo3","sequence1"),
new TestCallback ("Callback 3"));

//Here you have to wait till sandesha2 finishes the work, one options is to
//use Sandesha2 reports here.
}

As you can see in the code block you do not have to set any replyTo value. This will be automatically set to the WS-Addressing Anonymous URI or the WSRM Anonymous URI (described below).

Method 2:

In this method Apache Sandesha2 will not be offering a sequenceID from the client side. The server will have to send a CreateSequence message back for getting this (This CreateSequence message will be sent in the back channel of a MakeConnection message). The replyTo value of the messages sent from the client will take the WSRM Anonymous URI as defined by the WSRM 1.1 specification. A WSRM Anonymous URI takes the following format.

https://docs.oasis-open.org/ws-rx/wsmc/200702/anonymous?id=urn:uuid:ED5F61AC9

As you can see the URI is followed by a UUID value which is used to identify the logical back channel of a particular sequence.

In the policy configuration you have to set both the MakeConnection/Enabled policy and the MakeConnection/UseRMAnonURI policy to true. Also do not offer a sequenceID from your client code.

A code sample is given below.

private void runClient () throws Exception {

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(AXIS2_REPO,AXIS2_XML);
ServiceClient serviceClient = new ServiceClient (configContext,null);
Options clientOptions = new Options ();
serviceClient.setOptions(clientOptions);

serviceClient.engageModule(new QName ("sandesha2"));
clientOptions.setTo(new EndpointReference (toAddress));
clientOptions.setAction("urn:wsrm:EchoString");
clientOptions.setProperty(SandeshaClientConstants.RM_SPEC_VERSION,
Sandesha2Constants.SPEC_VERSIONS.v1_1);

serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo1","sequence1"),
new TestCallback ("Callback 1"));
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo2","sequence1"),
new TestCallback ("Callback 2"));

clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.sendReceiveNonBlocking(getEchoOMBlock("echo3","sequence1"),
new TestCallback ("Callback 3"));

//Here you have to wait till sandesha2 finishes the work, one options is to
//use Sandesha2 reports here.
}


Summary

Depending on your requirement and environment your application client is run, you may need to do some additional configurations when doing two-way reliable message interactions with a Apache Sandesha2-Axis2 system. This tutorial gives you a introduction to those configurations and explains the different ways you can write your client code.

Resources

[1] WS- Reliable Messaging 1.0 Specification, February 2005 : https://download.boulder.ibm.com/ibmdl/pub/software/dw/specs/ws-rm/ws-reliablemessaging200502.pdf

[2] Apache Sandesha2/Java : http://ws.apache.org/sandesha/sandesha2/

[3] Apache Axis2/Java : http://ws.apache.org/axis2/

[4] Apache Axis2/C : http://ws.apache.org/axis2/c

 

Author

Chamikara Jayalath, Senior Software Engineer - WSO2, chamikara at wso2 dot com

 

 

About Author

  • Chamikara Jayalath
  • Software Engineer
  • WSO2 Inc.