Due to the growing popularity of that feature request, we are thinking about adding a ready-made Request/Response mechanism in a future version of Lightstreamer.
In the meanhwile, the two options outlined by Mone work great.
Due to the growing popularity of that feature request, we are thinking about adding a ready-made Request/Response mechanism in a future version of Lightstreamer.
In the meanhwile, the two options outlined by Mone work great.
I want to expand on this topic through a simple example.Originally Posted by Mone
We will see a DataAdapter that responds to our requests with a list of 5 elements. Each element of the list has 2 fields, "key" that contains the name of the subscribed item plus a progressive and value that contains the string "Value" plus the same progressive.
First of all we compose the adapters.xml file for the adapter. We will use the LiteralBasedProvider as MetaDataProvider and our custom class FakeSubAdapter as DataProvider.
Moreover we configure the LiterBasedProvider with some parameters:This is the adapters.xml file
- distinct_snapshot_length - is the maximum number of snapshot events available for each item of the adapter. This is the value returned by the getDistinctSnapshotLength method; implementing your own MetaDataProvider this value could be different per each item.
- item_family_1 - is used as a Pattern to group items inside a family. Using .* we group in the same family all possible items.
- modes_for_item_family_1 - lists the admitted subscription modes for items pertaining to item_family_1. We admit only DISTINCT subscriptions.
Code xml:
<?xml version="1.0"?> <adapters_conf id="FAKESUB"> <metadata_provider> <adapter_class>com.lightstreamer.adapters.metadata.LiteralBasedProvider</adapter_class> <param name="distinct_snapshot_length">5</param> <param name="item_family_1">.*</param> <param name="modes_for_item_family_1">DISTINCT</param> </metadata_provider> <data_provider> <adapter_class>com.lightstreamer.req_resp_examples.fake_sub.FakeSubAdapter</adapter_class> </data_provider> </adapters_conf>
This is the DataProvider implementation (refer to comments inside the code for the explanations on how it works):
Code java:
package com.lightstreamer.req_resp_examples.fake_sub; import java.io.File; import java.util.HashMap; import java.util.Map; import com.lightstreamer.interfaces.data.DataProvider; import com.lightstreamer.interfaces.data.DataProviderException; import com.lightstreamer.interfaces.data.FailureException; import com.lightstreamer.interfaces.data.ItemEventListener; import com.lightstreamer.interfaces.data.SubscriptionException; public class FakeSubAdapter implements DataProvider { private ItemEventListener listener; return; } public void setListener(ItemEventListener listener) { //set the listener that will receive the updates this.listener = listener; } public void subscribe(String itemName, boolean needsIterator) throws SubscriptionException, FailureException { //create the thread that will handle the response. In a production //scenario you would probably use a pool, but for this example //create a new thread per each subscription is enough ResponseThread rt = new ResponseThread(itemName); //take trace of each subscribtion-related thread responseThreads.put(itemName,rt); //starts the thread that will send the response to clients rt.start(); } ResponseThread rt = (ResponseThread) responseThreads.get(itemName); if (rt != null) { //remove from subscription-related threads list responseThreads.remove(itemName); //if the thread is sending something (ie it has not finished to send the snapshot //that is our response) we stop it rt.end(); } } //in this case we send ONLY the snapshot, so it is obviously always available return true; } private volatile boolean exit = false; //save the itemName, we will use this name as part of the //updates this.itemName = itemName; } public void run() { //the response will be a DISTINCT table with 5 rows for(int i=1; i<=5 && !this.exit; i++) { //compose a Map to be passed to Lightstreamer kernel row.put("value","Value"+i); //pass the map to Lightstreamer kernel. This is an update. if (!this.exit)listener.update(this.itemName,row,true); } //send the endOfSnapshot signal (ie response is finished) if (!this.exit)listener.endOfSnapshot(this.itemName); } public void end() { //set a flag to stop updates this.exit = true; } } }
To be continued...
...continue
Now we need a client to test it, we implement a simple client with the web SDK.
In this page we put a <div> that is filled with the "response" and a "response is complete" message. There is also a form, fill the input field and press submit to make a request to the server. NOTE that since LiteralBasedProvider is used as MetaDataProvider, and since the demo uses the string inside the inputField as a group string, if you try to send a reuqest like "something somethingElse" you subscribe 2 items (in this case 2 "requests").
The page is made to be deployed under Lightstreamer internal web server so domain host and port are set to null.
To gather the desired data we use a NonVisualTable. Refer to the comments inside the code for further explanations
Code html:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <!-- inclusion of Lightstreamer web client library--> <script src="/LS/lscommons.js"></script> <script src="/LS/lspushpage.js"></script> </head> <body> <!--this div will show the response--> <div id="response"></div> <!--this form is used to call a method that makes the requests--> <form onsubmit="makeRequest();return false;"> <input id="requestQuery" type="text" /> <input type="submit" value="send request" /> </form> <script> /////////////////PushPage Configuration lsPage = new PushPage(); lsPage.context.setDomain(null); lsPage.onEngineReady = function(lsEngine) { lsEngine.connection.setLSHost(null); lsEngine.connection.setLSPort(null); lsEngine.connection.setAdapterName("FAKESUB"); lsEngine.changeStatus("STREAMING"); } lsPage.bind(); lsPage.loadEngineMinimal("/LS/lsengine.html"); /////////////////////////////////Request/Response handler var responseDiv = document.getElementById("response"); var requestQuery = document.getElementById("requestQuery"); function makeRequest() { //this is the string of the input field var request = requestQuery.value; //create a NonVisualTable, the item is the string of the input field, as fields //"key" and "value" and subscribe in DISTINCT mode var table = new NonVisualTable(request, new Array("key","value"), "DISTINCT"); //we need just the snapshot table.setSnapshotRequired(true); //this callback is called after subscription notification. We clear the responseDiv //so our response will be the only one inside it table.onStart = function() { responseDiv.innerHTML = ""; } //this callback is called per each received update. We compose a string with //the "key" and "value" fields and append this string to the responseDiv table.onItemUpdate = function(itemPos, itemObj, itemName) { var k = itemObj.getNewValue("key"); var v = itemObj.getNewValue("value"); responseDiv.innerHTML += k + ": " + v +"<br/>"; } //this callback is called when all snapshot events have been received. Put //this info in the responseDiv and remove the subscription table.onEndOfSnapshot = function(itemPos,itemName) { responseDiv.innerHTML += "Response COMPLETE " +itemPos+"<br/>"; //until we keep the table subscribed the data adapter will not be //called for others clients making the same request (ie Lightstreamer //kernel will handle directly responses for those clients). In this case //we don't remove the subscription. //lsPage.removeTable("req"); } //this callback is called in case some updates were lost. Put the info inside //the responseDiv table.onLostUpdates = function(itemPos,lostUpdates,itemName) { responseDiv.innerHTML += "We've lost updates!<br/>"; } //subscribe the table. In this demo we always use the same id ("req") to avoid //multiple concurrent requests. lsPage.addTable(table, "req"); } </script> </body> </html>
The sources of the demo are also available as a downloadable zip attacched to this post.
Bookmarks