SitePen Support
Webtide

“Hello World” for Flex AMF with Lightstreamer

by Alessandro AlinoneMarch 7th, 2010

In this fourth installment of the “Hello World with Lightstreamer” series, we will focus on a new feature that was released with Lightstreamer Server version 3.6: Action Message Format (AMF) support for Flex applications.

First, a quick recap of the previous installments:

  1. “Hello World” with Lightstreamer: An introduction to Lightstreamer’s data model, Web Client API, and Java Data Adapters API.
  2. “Hello World”
    for .NET with Lightstreamer
    : The .NET API version of the Data Adapter used in the “Hello World” application, showing both a C# and a Visual Basic port.
  3. “Hello World” for Sockets with Lightstreamer: The TCP-socket-based version of the Data Adapter, suitable for implementation in other languages (PHP, Python, Perl, etc).

Basically, Lightstreamer Server can be seen as a “technology hub” for data push, where you can mix different technologies on the client-side and on the server-side to exchange real-time messages.

Lightstreamer Hub

In this article, we will delve into the “Flex on the client-side, Java on the server-side” scenario.

In particular, Simone Fabiano will show you how to push binary ActionScript objects directly to your Flex application in real-time, using AMF. Action Message Format is used for serializing binary ActionScript objects in order to transmit them over the wire.

As you may recall form the first installment, Lightstreamer data push model is based on items made up of fields. A client can subscribe to many items, each with its own schema (a set of fields). Each field is usually a text string with an arbitrary length (from a few characters to large data structures, perhaps based on XML, JSON, etc.). With this new Lightstreamer feature, you can now put a full AMF object into any field and have it pushed from the server to the client in real binary format (with no re-encondings).

The Flex Client library for Lightstreamer has been used for one of the major dealing platforms in the finance field and has undergone many cycles of improvements to make it completely production resilient. We were asked to add native support for AMF objects to improve the performance when streaming complex data structures. So now you can push both text-based items and object-based items to the same Flex application.

When approaching the Lightstreamer data model, it is important to choose the right trade-off between using fine-grained fields and coarse objects. You could map each individual atomic piece of information to a Lightstreamer field, thus using many fields and items, or you could map all your data to a single field of a single item. This applies to both text-based fields (where you can encode coarse objects via JSON, XML, etc.) and object-based fields (via AMF). Usually, going for fine-grained fields is better, because you let Lightstreamer know more about your data structure, so that it will be able to apply optimization mechanisms, like conflation and delta delivery. On the other hand, if you go for huge opaque objects, Lightstreamer will be used more as a blind pipe. But in both cases, you will still benefit from other features, like bandwidth allocation and dynamic throttling. All intermediate scenarios are possible too.

In the tutorial below, Simone will use a single field containing an AMF object derived from a JavaBean. To encode a JavaBean as an AMF object, several ready-made libraries exist. Here we will leverage BlazeDS.

AMF Lightstreamer Tutorial

by Simone Fabiano, Software Engineer at Lightstreamer

Simone FabianoAlessandro asked me to write a simple “Hello World” example to show how to use AMF with our new Flex client library (docs). We will create a JavaBean on the server-side and then use it on the client-side.

For this tutorial, I’m assuming you have already read the Basic Hello World example, or that you are already familiar with Lightstreamer concepts.

On the client, the result of this tutorial will be quite similar to the one obtained with the original Hello World by Alessandro, but in Flex: we’ll get a string alternating some different values (Hello AMF World) and a timestamp. On the server-side, data will be encapsulated into a JavaBean containing a String and a Date instance. This bean will be translated into a byte array and then injected into the Lightstreamer kernel as a single field, instead of being spread over different fields as simple strings (as the original adapter does). Here lies the power of AMF, as you will be able to push even complex JavaBeans to your Flex clients with ease.

Gather Stuff

  • First of all you’ll need a browser, a Flash player, and a JDK: hopefully you already have those :)
  • You’ll need Lightstreamer Server 3.6 (Presto or Vivace) and Lightstreamer Flex Client 2.0. You can download them from the Lightstreamer web site.
  • You’ll have to compile a Flex application, so you’ll need either the Flex Builder or the Flex SDK (the example works with Flex 3 and Flex 4; use Flex 4 only if you’re going to use it with the Flex Builder, otherwise you may have problems with the mxmlc
  • The conversion from Java beans to an AMF-compatible byte array is performed by a couple of the BlazeDS libraries. Download BlazeDS (binary distribution) and extract flex-messaging-common.jar and flex-messaging-core.jar from it (the downloaded zip contains a .war file, open it with an archive manager and locate the needed libraries under “WEB-INF/lib/”).

Let’s Code the Front-End(s)

The front-end of our application will be a simple .mxml file compiled into a .swf. Open your text editor and start writing a mxml file (notice the creationComplete event):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   creationComplete="init()">

Then add a TextArea instance (myTextArea), where we’ll put the updates:

<mx:TextArea width="693" height="56" id="myTextArea"
   fontSize="25" fontWeight="bold" text="loading..."/>

Now to Actionscript… We need an init method to initialize our client and subscribe to our table. We’ll also configure a DateFormatter instance to print nice-looking dates.

public function init():void {
  dateFormatter.formatString = "MM/DD/YY J:NN:SS"; 
 
  var cInfo:ConnectionInfo = new ConnectionInfo();
  cInfo.server = "localhost";
  cInfo.adapterSet = "AMFHELLOWORLD";
 
  var client:LSClient = new LSClient();
 
  var nonVisualTable:NonVisualTable =
     new NonVisualTable(["greetings"],["AMF_field"],"MERGE",true);
  nonVisualTable.addEventListener(
    NonVisualItemUpdateEvent.NON_VISUAL_ITEM_UPDATE,onChange);
  client.subscribeTable(nonVisualTable);
 
  client.openConnection(cInfo);
}

The ConnectionInfo instance defines how we connect to the server and the adapter set that will handle our requests. In this case we’ll deploy our application on tje Lightstreamer internal web server so the server has to be set to “localhost”. The adapter set in use will be called “AMFHELLOWORLD”.

The LSClient instance is the core of the Lightstreamer Flex client library. It handles the connection to the server and the subscription/unsubscription of tables.

The NonVisualTable represents the subscription we’re going to make. It is a mono-item (”greetings”) and mono-field (”AMF_field”) MERGE subscription. The fourth parameter in the constructors tells the server that we’re looking for binary data, namely AMF.

Next, we need the onChange callback to handle the updates and show them on screen. We’ll just extract the single field and, since it’s a Bean, expand it, format the date an put everything on the TextArea previously created:

public function onChange(evt:NonVisualItemUpdateEvent):void {
  if (evt.isFieldChanged("AMF_field")) {
    var obj:* = evt.getFieldValue("AMF_field");
    myTextArea.text = dateFormatter.format(obj.now) + " " + obj.hello;
  }
}

Download the complete source of the Flex client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
  creationComplete="init()">
 
  <mx:TextArea width="693" height="56" id="myTextArea" 
    fontSize="25" fontWeight="bold" text="loading..."/>
 
  <mx:Script><![CDATA[
    import mx.formatters.DateFormatter;
    import com.lightstreamer.as_client.ConnectionInfo;
    import com.lightstreamer.as_client.LSClient;
    import com.lightstreamer.as_client.NonVisualTable;
    import com.lightstreamer.as_client.events.NonVisualItemUpdateEvent;
 
    public var dateFormatter:DateFormatter = new DateFormatter();
 
    public function init():void {
      dateFormatter.formatString = "MM/DD/YY J:NN:SS"; 
 
      var cInfo:ConnectionInfo = new ConnectionInfo();
      cInfo.server = "localhost";
      cInfo.adapterSet = "AMFHELLOWORLD";
 
      var client:LSClient = new LSClient();
 
      var nonVisualTable:NonVisualTable = 
        new NonVisualTable(["greetings"],
         ["AMF_field"],"MERGE",true);
      nonVisualTable.addEventListener(
        NonVisualItemUpdateEvent.NON_VISUAL_ITEM_UPDATE,
        onChange);
      client.subscribeTable(nonVisualTable);
 
      client.openConnection(cInfo);
    }
 
    public function onChange(evt:NonVisualItemUpdateEvent):void {
      if (evt.isFieldChanged("AMF_field")) {
        var obj:* = evt.getFieldValue("AMF_field");
        myTextArea.text = 
          dateFormatter.format(obj.now) + " " + obj.hello;
      }
    }    
 
  ]]>
  </mx:Script>
</mx:Application>

If you prefer a DataGrid in place of the TextArea, don’t worry, the substitution is really easy. First we replace the TextArea with a DataGrid (myDataGrid) with two columns. Each column is associated to a property of the bean sent by the server, so “AMF_field” represents the entire bean (the field of our subscription) and “AMF_field.hello” represents the “hello” property of the bean. We add a labelFunction to one of the columns so that we’ll be able to format the received date.

<mx:DataGrid width="479" x="447.5" height="91" id="helloView" y="12" >
  <mx:columns>
    <mx:DataGridColumn dataField="AMF_field.now" 
      headerText="Time" labelFunction="formatDate" />
    <mx:DataGridColumn dataField="AMF_field.hello" headerText="Hello"/>
  </mx:columns>
</mx:DataGrid>

Then, we need something to be bound with the DataGrid. Luckily enough, our VisualTable class extends the ArrayCollection native class. so that it can be bound to a DataGrid to update the view automagically. We have to put our VisualTable in the global space so that we can declare the [Bindable] metadata on it.

[Bindable] public var myTable:VisualTable;

Finally, we have to initialize this table. The code is very similar to the one used to initialize the NonVisualTable, but we don’t need to add any event handlers. Instead, we bind our VisualTable and the DataGrid assigning our Bindable VisualTable to the “dataProvider” property of the DataGrid.

var myTable:VisualTable =
  new VisualTable(["greetings"],["AMF_field"],"MERGE",true);
helloView.dataProvider = myTable;

Download the complete modified source of the Flex client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
  creationComplete="init()">
 
 <mx:DataGrid width="479" x="447.5" height="91" id="helloView" y="12" >
  <mx:columns>
    <mx:DataGridColumn dataField="AMF_field.now" 
      headerText="Time" labelFunction="formatDate" />
    <mx:DataGridColumn dataField="AMF_field.hello" headerText="Hello"/>
  </mx:columns>
 </mx:DataGrid>
 
 <mx:Script> <![CDATA[
	import mx.formatters.DateFormatter;
	import com.lightstreamer.as_client.VisualTable;
	import com.lightstreamer.as_client.ConnectionInfo;
	import com.lightstreamer.as_client.LSClient;
 
	public var dateFormatter:DateFormatter = new DateFormatter();
 
	[Bindable]
	public var myTable:VisualTable;
 
	public function init():void {
		dateFormatter.formatString = "MM/DD/YY J:NN:SS"; 
 
		var cInfo:ConnectionInfo = new ConnectionInfo();
		cInfo.server = "localhost";
		cInfo.adapterSet = "AMFHELLOWORLD";
 
		var client:LSClient = new LSClient();
 
		var myTable:VisualTable = 
		  new VisualTable(["greetings"], 
		    ["AMF_field"],"MERGE",true);
		helloView.dataProvider = myTable;
		client.subscribeTable(myTable);
 
		client.openConnection(cInfo);
	}
 
	public function formatDate(item:Object,
          column:DataGridColumn):String {
		return dateFormatter.format(item.AMF_field.now);
	}
 ]]></mx:Script>
</mx:Application>

The mxml file, as is, is quite useless, so, save it as “AMFHelloWorld.mxml” and compile it into a swf file using the Flex SDK:

FLEX_SDK_HOME/bin/mxmlc AMFHelloWorld.mxml 
  -output AMFHelloWorld.swf 
  -library-path+=
    LS_HOME/DOCS-SDKs/sdk_client_flash_flex(native_as)
    /lib/Lightstreamer_as_client.swc

Note that if you’re using a 64-bit JVM you may have some issues running mxmlc; use a 32-bit JVM (mxmlc makes use of the JAVA_HOME environment variable to choose the JVM).

Server-Side

Now it’s time to write the adapter. We’ll reuse most of the code of the first Hello World. We’ll add a static property and a static utility method to the class we’ve renamed into “AMFHelloWorld”.

private static SerializationContext context = 
  SerializationContext.getSerializationContext();
 
public static byte[] toAMF(Object bean) { 
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  Amf3Output output = new Amf3Output(context);
  output.setOutputStream(baos);
  try {
    output.writeObject(bean);
    output.flush();
    output.close();
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  return baos.toByteArray();
}

The toAMF method receives an Object instance and converts it into an AMF byte array using the Amf3Output class. You can find a list of the conversions performed to switch from Java to AMF in the ActionMessageOutput class javadoc.

In this case we’re going to use a Java bean. Note that the AMF3Output class javadoc is currently not linked/listed on BlazeDS classes list (they forgot?). You can reach it anyway, at the logical directory.

Once the conversion method is in place, we can add the bean we want to send to the clients. We will prepare a simple bean containing only two properties, a String and a Date:

public class HelloBean implements java.io.Serializable {
 
  private static final long serialVersionUID = 7965747352089964767L;
  private String hello;
  private Date now;
 
  public HelloBean() {
  }
 
  public String getHello() {
    return hello;
  }
 
  public void setHello(String hello) {
    this.hello = hello;
  }
 
  public Date getNow() {
    return now;
  }
 
  public void setNow(Date now) {
    this.now = now;
  }
}

Finally, we will replace the run method of the GreetingThread inner class with a different implementation that handles our bean:

public void run() {
  int c = 0;
  Random rand = new Random();
  HelloBean testBean = new HelloBean();
  while(go) {
    Map<String,byte[]> data = new HashMap<String,byte[]>();
 
    testBean.setHello(c % 3 == 0 ? "Hello" : c % 3 == 1 ? "AMF" : "World");
    testBean.setNow(new Date());
 
    data.put("AMF_field", toAMF(testBean));
 
    listener.smartUpdate(itemHandle, data, false);
    c++;
    try {
        Thread.sleep(1000 + rand.nextInt(2000));
    } catch (InterruptedException e) {
    }
  }
}

As you can see, there’s nothing extremely complicated here, we just convert the instance of our bean through the toAMF method and inject it in a Map as if it was a simple String. Then, in turn, the Map is injected into the Lightstreamer kernel to make its way to the clients.

Download the complete Java Adapter source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
 
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
 
import com.lightstreamer.interfaces.data.DataProviderException;
import com.lightstreamer.interfaces.data.FailureException;
import com.lightstreamer.interfaces.data.ItemEventListener;
import com.lightstreamer.interfaces.data.SmartDataProvider;
import com.lightstreamer.interfaces.data.SubscriptionException;
 
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Output;
 
 
public class AMFHelloWorld implements SmartDataProvider {
  private ItemEventListener listener;
  private volatile GreetingsThread gt;
 
  public void init(Map params, File configDir) 
    throws DataProviderException {
  }
 
  public boolean isSnapshotAvailable(String itemName) 
    throws SubscriptionException {
    return false;
  }
 
  public void setListener(ItemEventListener listener) {
    this.listener = listener;
  }
 
  public void subscribe(String itemName, 
    Object itemHandle, boolean needsIterator) 
        throws SubscriptionException, FailureException {
    if (itemName.equals("greetings")) {
      gt = new GreetingsThread(itemHandle);
      gt.start();
    }
  }
 
  public void subscribe(String itemName, 
    boolean needsIterator)
        throws SubscriptionException, FailureException {
  }
 
  public void unsubscribe(String itemName) 
    throws SubscriptionException,
        FailureException {
    if (itemName.equals("greetings") && gt != null) {
      gt.go = false;
    }
  }
 
  private static SerializationContext context = 
    SerializationContext.getSerializationContext();
 
  public static byte[] toAMF(Object bean) { 
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Amf3Output output = new Amf3Output(context);
    output.setOutputStream(baos);
    try {
      output.writeObject(bean);
      output.flush();
      output.close();
    } catch (IOException e1) {
      e1.printStackTrace();
    }
    return baos.toByteArray();
  }
 
  class GreetingsThread extends Thread {
 
    private final Object itemHandle;
 
    public volatile boolean go = true;
 
    public GreetingsThread(Object itemHandle) {
      this.itemHandle = itemHandle;
    }
 
    public void run() {
      int c = 0;
      Random rand = new Random();
      HelloBean testBean = new HelloBean();
      while(go) {
        Map<String,byte[]> data = 
          new HashMap<String,byte[]>();
 
        testBean.setHello(c % 3 == 0 ? 
          "Hello" : c % 3 == 1 ? "AMF" : "World");
        testBean.setNow(new Date());
 
        data.put("AMF_field", toAMF(testBean));
 
        listener.smartUpdate(itemHandle, data, false);
        c++;
        try {
            Thread.sleep(1000 + rand.nextInt(2000));
        } catch (InterruptedException e) {
        }
      }
    }
 
  }
 
  public class HelloBean implements java.io.Serializable {
 
  private static final long serialVersionUID = 
    7965747352089964767L;
    private String hello;
    private Date now;
 
    public HelloBean() {
    }
 
    public String getHello() {
      return hello;
    }
 
    public void setHello(String hello) {
      this.hello = hello;
    }
 
    public Date getNow() {
      return now;
    }
 
    public void setNow(Date now) {
      this.now = now;
    }
  }
}

Deploy the Adapter

Now just compile the .java file as you would compile any other java application. Remember to add to the classpath the “ls-adapter-interface.jar” file (from LS_HOME/DOCS-SDKs/sdk_adapter_java/lib), “flex-messaging-common.jar” and “flex-messaging-core.jar” (from BlazeDS). You will obtain three class files: AMFHelloWorld.class, AMFHelloWorld$HelloBean.class and AMFHelloWorld$GreetingsThread.class. Now create an “AMFHelloWorld” folder under “LS_HOME/adapters” and a “classes” folder inside it: put the .class files there.

Finally, create an adapters.xml file under the LS_HOME/adapters/AMFHelloWorld folder:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
 
<adapters_conf id="AMFHELLOWORLD">
  <metadata_provider>
    <adapter_class>
      com.lightstreamer.adapters.metadata.LiteralBasedProvider
    </adapter_class>
  </metadata_provider>
  <data_provider>
    <adapter_class>AMFHelloWorld</adapter_class>
  </data_provider>
</adapters_conf>

As anticipated, while assembling the client we’re going to use “AMFHELLOWORLD” as the adapter set name, while we’ll use the classic LiteralBasedProvider as our Metadata Adapter and our brand new AMFHelloWorld class as our Data Adapter.

Deploy the Client

The client is made up only by the compiled swf file, so grab that file and put it under the “LS_HOME/pages” folder, that’s all.

Run!

Start the Lightstreamer server and point your browser to:
http://localhost:8080/AMFHelloWorld.swf
Enjoy :)

Final Notes

You’ve seen how to push Objects instead of Strings from a Lightstreamer server to a Flex client. You can exploit this technique to push complex data structures, but obviously, doing so you’ll lose some of the optimizations offered by Lightstreamer protocol. For example, the merging algorithm (of the MERGE mode) is applied to the entire bean instead of being applied to each single field, so that every time a property within the bean changes, the entire bean is pushed to the client, not only the changed value. As with anything regarding engineering you’ll have to choose the trade-off that optimizes properly for your application.

Please also consider that the Flex client library in this tutorial is not available with the Moderato version of Lightstreamer Server, so you may want to use a trial license for Lightstreamer Presto/Vivace to experiment with it.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]
Orbited

5 Responses to ““Hello World” for Flex AMF with Lightstreamer”

  1. saaron Says:

    I have tried compiling your example. I can connect to server but the subscribe() item in the adapter does not get called. What are common reasons for this behavior?

  2. saaron Says:

    BTW I am building a flex application

  3. Alessandro Alinone Says:

    @saaron: For the sake of the other readers, I hope you don’t mind if I paste here the update you sent us via email: “I have solved the problem. Apparently I missed the step to setup the control port in the client. I changed the default port on which LS runs from 8080 to 8085 but in my code I only called the cInfo.setPort(8085); method. However after calling the cInfo.setControlPort(8085); method, the client was able to connect successfully.”

  4. Parkash Kumar Says:

    AA,

    I am currently using Light Streaming feature using Flex. Every thing is going right except one thing that is I loss the previous data.
    I have a webservice that returns some data. I use this data to populate the grid. Then I connect to AMS once i do, the data that was present on the grid gets vanished and the data from AMS server populates on the grid. What I want to do is that I want to keep all the data as it is and when connect to AMS only those columns should be updated whose data is available on the AMS server.

  5. Mone Says:

    I guess that you switch from the web service data to AMS data changing the dataProvider of your grid, that will change the model of the grid with one that is initially empty.

    to mix things together you may implement a class that extends ArrayCollection and that mixes together the data from your web service and the data from AMS. Then use that as the model of your grid.

Leave a Reply



Copyright 2014 Comet Daily, LLC. All Rights Reserved