cvikander
I currently work on an application using Adobe's Live Cycle Data Services and we are looking into switching to LightStreamer as our messaging backend. The application is written in Flex and uses a Java back end to communicate with our database. We're also re-writing components of the Flex application in HTML5 using websockets, which is why we are looking at LightStreamer as an alternative to LiveCycle.
I'm hoping there is someone who has gone down this same path as I'm looking to utilize as much of the existing Java code as I can, however it was reliant on Remote Services and Messaging Endpoints as is the paradigm of LiveCycle. I'm trying to wrap my head around writing adapters to use these existing services and I understand a remote call can be replaced with a snapshot of an item, but I'm not fully sure on the implementation.
marco.testa
Hi
If I've well understood, what you are asking is how to implement the request reply paradigm with Lightstreamer, stated Lightstreamer is specialized for Publish/Subscribe, rather than Request/Response.
Even if there is not a direct way to implement a request/reply communication, it is possible to use Lightstreamer in a Request/reply fashion through one of the following techniques:
The first technique is to use a subscription request as an actual request for specific data, and use the first published update as the actual reply to the request.
Basically the idea is to use a disposable subscription, where the subscribed item name will represent the request.
The answer will be provided by the first (and only) published update, provided as snapshot.
Once the response is received, the client unsubscribes the given disposable item, since no other update will be published for that item.
If the response depends on the user sending the request and must be different for each user, we can leverage the getItem method of the MetadataProvired.
As example consider the case where an integer number must be summed to the user name, and the user name is a number too.
Most of the error handling is obviously missing to keep the code as small as possible.
We use the LiteralBasedProvider as base class for the MyMetadataAdapter
Client Code:
[SYNTAX=JS] var userName = Math.round(Math.random()*100);
console.log("user: " + userName);
require(["LightstreamerClient","Subscription"],
function(LightstreamerClient,Subscription) {
var client = new LightstreamerClient("http://localhost:8080","MY_ADAPTERS");
client.connectionDetails.setUser(userName);
client.connect();
var requestSubscription = new Subscription("MERGE","REQ:42",["response"]);
requestSubscription.setRequestedSnapshot("yes");
requestSubscription.addListener({
onItemUpdate: function(update) {
console.log("response: " + update.getValue("response"));
client.unsubscribe(requestSubscription);
}
});
client.subscribe(requestSubscription);
});
[/SYNTAX]
Metadata Adapter Server code
[SYNTAX=JAVA] public class MyMetadataAdapter extends LiteralBasedProvider {
public String[] getItems(String user, String sessionID, String group)
throws ItemsException {
String[] split = super.getItems(user,sessionID,group);
for (int i = 0; i<split.length; i++) {
if (split.startsWith("REQ:")) {
split = "RESP:"+user+":"+split.substring(4);
} else if (group.startsWith("RESP:")) {
// protection from unauthorized use of user-specific items
throw new ItemsException("Unexpected item name");
}
}
return split;
}
}
[/SYNTAX]
Data Adapter Server code:
[SYNTAX=JAVA] public class MyDataAdapter implements SmartDataProvider {
private ItemEventListener listener;
@Override
public void setListener(ItemEventListener listener) {
this.listener = listener;
}
@Override
public void subscribe(String itemName, Object itemHandle, boolean arg2)
throws SubscriptionException, FailureException {
String[] request = itemName.split(":");
if (!request[0].equals("RESP")) {
throw new SubscriptionException("No such item");
}
//in this simple example the response is the request (request[2]) + the username (request[1])
//if obtaining the answer is actually a long process this code should be executed in a different thread
Map<String,String> resp = new HashMap<String,String>();
int sum = Integer.valueOf(request[1])+Integer.valueOf(request[2]);
resp.put("response", String.valueOf(sum));
this.listener.smartUpdate(itemHandle, resp, true);
}
[cut]
}
[/SYNTAX]
To be continued ...
marco.testa
... continue
The second technique uses a predetermined 'response' item, where all the responses will be published.
The client subscribes to the 'response' item and it keeps the subscription active for the entire session.
The client sends the request with the sendMessage and the reply will be received as an update on this predetermined item.
The 'response' item must be subscribed in RAW mode to avoid to merge and lose responses.
Actually there must be one distinct item for each session, because the client must receive the responses only for the request it submit. This requires to use the getItems method of the MetadataProvider to replace the predetermined 'response' item with a version decorated with the sessionID and thus unique per session.
Each request, received by the notifyUserMessage method of the MetadataProvider will be passed to the DataAdapter with the corresponding sessionID, so that the DataAdapter is able to publish the response as an update on the corresponding item for the sessionID.
This implies also that the MetadataProvider must know the DataProvider, and must be able to route the sendMessage requests to it.
Since the response is received asynchronously by the client, this solution requires also that the client must have a way to match the responses with the corresponding requests.
The single predetermined 'response' item technique uses network resources more efficiently.
- on the single item approach you'll send one request to the server (the subscription) plus one request per each request (the sendMessage)
- on the disposable item approach you'll send two requests to the server per each request (subscribe+unsubscribe)
Note however that on websocket sessions the requests are all sent over the same wesocket so that there is not the overhead of a classic http request to a server.
And note also that, even if on the server the handling of a subscription is slightly heavier than the handling of a message, using the disposable subscription will spare some logic, so that probably there will be not much processing differences between the two options.
The single predetermined 'response' item technique may let you spares some messages but since it is slightly more complex than the disposable item one, it should be used only in case of a hight rate of requests and you have already verified the first solution does not perform well.
There is also a third technique which is actually is a trick that exploits the capability of the sendMessage to receive errors messages from the metadata adapter.
The use of this technique is not encouraged but may be handy in some cases.
Be aware that this solution can only be used if the notifyUserMessage method in the Metada Adapter can actually execute fast.
As before the client sends the request with the sendMessage API. but the server will process the request directly in the notifyUserMessage method of the MetadataProvider interface, and will reply throwing a CreditsException, bearing the response as error message.
Client code:
[SYNTAX=JS]var userName = Math.round(Math.random()*100);
console.log("user: " + userName);
require(["LightstreamerClient","Subscription"],
function(LightstreamerClient,Subscription) {
var client = new LightstreamerClient("http://localhost:8080","MY_ADAPTERS");
client.connectionDetails.setUser(userName);
client.connect();
client.addListener({
onStatusChange:function(newStatus) {
if (newStatus.indexOf("CONNECTED:") == 0) {
//in this example code we send the request here to be certain to be connected to the LS server
//doing so actually makes the client send more requests, thus running this code will result
//in more than one request/response interaction
client.sendMessage("42",null,null,{
onDeny: function(originalMex, code, resp) {
console.log("response: "+resp);
}
});
}
}
});
});
[/SYNTAX]
Server code: Metadata Adapter
[SYNTAX=JAVA]public class MyMetadataAdapter extends LiteralBasedProvider {
public void notifyUserMessage(String user, String sessionID, String message)
throws CreditsException, NotificationException {
//if it takes time to obtain the answer to the request this
//approach should not be used
int res = Integer.valueOf(message) + Integer.valueOf(user);
throw new CreditsException(-1,String.valueOf(res));
}
}[/SYNTAX]
hth,
Marco