6.4. A BlazeDS-Powered Messaging Application

This section covers most things about BlazeDS messaging. The explanations and discussion are in the context of a sample application.

6.4.1. The Application Use Case

Before I start with the application itself, it may be prudent to explain what the application does. This example application is a tool for effectively watching changing currency exchange rates. It does the following:

  • Allows you to set up currency pairs to watch for exchange rate changes

  • Updates the rates in real time on the basis of the incoming rate change messages

  • Plots the changing rates on a line chart in real time

  • Provides a chat window to connect with your investment advisor for questions, clarifications, and advisory services related to investment opportunities created by these rate changes

  • Allows you to buy or sell either side of a currency pair

Such an application in real life would connect to a currency OTC (over the counter) exchange, an interbank system, or market data provider for real-time price quotes. It would then connect to trading venues or broker-dealer systems for trade execution. In our sample application, I don't do any of this, the reasons being:

  • Access to trading venues, market data, and broker networks is neither trivial nor inexpensive.

  • The complexity of connectivity to these external systems can overwhelm our sample application and overshadow the BlazeDS messaging characteristics that I want to highlight.

The dilemma though is that, without the updating exchange rates and the interactions thereafter, the application is pretty useless from a messaging standpoint. So, I have created a simple simulation program that generates some random changes to the rates, which BlazeDS pushes up to the Flex client. In order to further reduce the complexity and stay focused on the core issues, I have preselected a set of currency pairs for the watch list and excluded the feature to add and remove elements to/from the list. With that information let's start looking at the example.

I will build the application iteratively and walk you through all the interesting steps as I progress from the clean slate to the running application.

6.4.2. The First Few Steps

The example's exposition starts with setting up a Flex project, drawing out the initial view, and initializing the application with test data. As with earlier examples in this book, the Flex Builder is utilized for convenience, although the application will compile and work fine without it as well.

To get things started, create a Flex project and choose the J2EE server option as you create it. I use the JBoss AS (application server) for my example, but you have the choice to go with any other application server that supports JMS. If you decide to use Apache Tomcat, then remember to wire up an external JMS provider like ActiveMQ with it so that you can use the JMS infrastructure and the API. JMS is essential for this application. It is used with BlazeDS to push the exchange rate updates from the Java server side to the Flex client.

In Flex Builder, where I chose to create a joint Flex and Java project, using WTP, the structure and the folders appear as shown in Figure 6-9.

Figure 6.9. Figure 6-9

The Flex source resides in flex_src; the web application code is in the WebContent folder. Within this folder are the usual META-INF and WEB-INF folders. Java Servlet– and JSP-based web applications are commonly bundled as war files. "war" is an acronym for Web Archive file and is a special type of Java jar archive file. It's standard practice to bundle web components into a war file for deployment. The war file format is supported by all Java application servers.

BlazeDS is a Java Servlet–based web application, so the content being bundled as a war file is appropriate for deployment. A war file can be created using the jar cvf path owarfileWarFileName.war -C pathoffiles oarchive command. In JBoss a war file can be deployed by simply copying the war file over to the server/default/deploy folder of a JBoss AS installation. In some situations, BlazeDS may form part of a larger enterprise application that uses middle tier components like Enterprise Java Beans (EJBs). Java enterprise applications are bundled as "ear" files. "ear" is an acronym for Enterprise ARchive, and it is yet another special form of the Java jar file.

The META-INF folder within WebContent is where the manifest file for the war file resides. Manifest files define the content of a Java jar file and work as its metadata. You will almost never need to directly play with the manifest file in the context of BlazeDS.

The WEB-INF folder, which resides side by side with the META-INF folder, is the most important folder and holds the following:

  • The Java classes that implement the remote service or work in association with it.

  • The services-config.xml file and its associated configuration files.

  • web.xml, the necessary deployment descriptor that defines how a Java Servlet–based web application is deployed and determines how requests get routed to it.

  • All the external library archive files required for the web application to work. These files are in the lib folder. All compiled classes bundled as a library are in the lib folder, and all compiled classes that are standalone are in the classes folder.

The compiled Flex code resides in the bin-debug folder and finally gets bundled at the root of the war file. There are a few additional files that hold assets and the metadata information for Flex Builder and WTP, but we will not delve into those here.

Once the project is all set up, go to the application's MXML file and start coding the initial version of the view. I named my project XchangeRate, and my application's MXML file is named XchangeRate.mxml. I only have a plain and simple DataGrid component at the beginning, which is in line with my idea to first get the static initial data and plumb that using the remoting service facility that BlazeDS provides. In many enterprise applications, you will use the remoting and messaging features in association with each other, as I do in this example.

The code for my initial screen is only a few lines:

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

    <mx:RemoteObject id="xRateDS" destination="xrate"/>

    <mx:DataGrid id="xRateDG" dataProvider="{xRateDS.getxRateData.lastResult}" />

</mx:Application>

If you look at the code carefully, you will notice that the data for the data grid is being fetched through a remoting service. Essentially, the result of a remote procedure call is set as the dataProvider of the DataGrid. The remote service itself is accessible via its destination handle, which in this case is xrate. You know from Chapter 4 how the remoting service works, and if you are feeling rusty, you may want to peek back into that chapter.

The destination name and its attributes are defined in remoting-config.xml, which by reference is part of the services-config.xml. The services-config.xml file is the configuration file that BlazeDS uses to set up and initialize its core artifacts, including channels, endpoints, services, destinations, and adapters.

I added the following to remoting-config.xml:

<destination id="xrate">
        <properties>
            <source>problazeds.ch06.XchangeRateDataService</source>
        </properties>
    </destination>

The remoting-config.xml configuration binds the XchangeRateDataService Java class as the class behind the remote service that is accessible via the xrate destination. In this case, the class returns the initial data, which is, essentially, the following data for seven currency pairs:

  • symbol — The currency pair identifier

  • bid — Buy price

  • offer — Sell price

  • lastUpdated — Time when the data was available

The source for this service class is shown in Listing 6-1. The currency pair data points are represented in an object-oriented format. Listing 6-2 is the source for this object-oriented representation.

Example 6.1. The Remote Service Class That Returns the Initial Data
package problazeds.ch06;

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

public class XchangeRateDataService
{
    public List getxRateData() {
        List list = new ArrayList();
        list.add(new XchangeRatePair("EUR.USD", 1.3238, 1.3241, new Date()));
        list.add(new XchangeRatePair("USD.JPY", 98.25, 98.27, new Date()));
        list.add(new XchangeRatePair("EUR.JPY", 130.08, 130.13, new Date()));
        list.add(new XchangeRatePair("GBP.USD", 1.4727, 1.4731, new Date()));
        list.add(new XchangeRatePair("USD.CHF", 1.1396, 1.1400, new Date()));
        list.add(new XchangeRatePair("EUR.CHF", 1.5091, 1.5094, new Date()));
        list.add(new XchangeRatePair("EUR.GBP", 0.89844, 0.89890, new Date()));

        return list;

    }

}

Example 6.2. A DTO representing the Exchange Rate Data
package problazeds.ch06;

import java.util.Date;

public class XchangeRatePair
{
    XchangeRatePair() {

    }

    XchangeRatePair(String symbol, double bid,
            double offer,Date lastUpdated) {
        this.symbol = symbol;
        this.bid = bid;
        this.offer = offer;
        this.lastUpdated = lastUpdated;

    }



    private String symbol;
    private double bid;
    private double offer;
    private Date lastUpdated;
    public String getSymbol()
    {
        return symbol;
    }

    public void setSymbol(String symbol)
    {
        this.symbol = symbol;
    }

    public double getBid()
    {
        return bid;
    }

    public void setBid(double bid)
    {
        this.bid = bid;
    }

    public double getOffer()
    {
        return offer;
    }

    public void setOffer(double offer)

{
        this.offer = offer;
    }

    public Date getLastUpdated()
    {
        return lastUpdated;
    }

    public void setLastUpdated(Date lastUpdated)
    {
        this.lastUpdated = lastUpdated;
    }

}

The only other piece of code so far is the AS3 counterpart of the exchange rate object, which is as follows:

package problazeds.ch06
{
    [RemoteClass(alias="problazeds.ch06.XchangeRatePair")]
    public class XchangeRatePair
    {
        public var symbol:String;
        public var bid:Number;
        public var offer:Number;
        public var lastUpdated:Date;
    }
}

I don't use this class yet, but it will come handy later. Also, so far there is no usage of the messaging service, but that's what is going to come next.

6.4.3. Sprucing Up the Client Code

To get closer to our example application, I recraft the view component and spruce it up with a lot of additional plumbing to get it ready to listen for updates and process them once it receives them.

As a first move, I introduce a LineChart component. A LineChart draws out a line joining its data points. Such a chart can accommodate more than one series on the same chart and that's something I am going to leverage. I will chart the bid and the offer quotes for a currency pair on a single component, right next to each other. That way as the updates come in, the spread between the two gets charted out in real-time. The code for the line chart is:

<mx:LineChart id="xRateLC" width="100%" height="50%">
        <mx:series>
            <mx:LineSeries
                yField="bid"
                displayName="Bid"/>
            <mx:LineSeries
                yField="offer"

displayName="Offer"/>
        </mx:series>
    </mx:LineChart>

This chart is redrawn on every update. The chart is drawn using a custom function called drawChart, which is:

private function drawChart():void {
                    xRateLC.dataProvider = xRates[xRateDG.selectedItem.symbol];
    }

If you look at this rather small but useful function, you will notice that the chart's data is based on the selected item in the data grid. Every currency pair in the data grid has an evolving pair of data points for its bid and offer quotes. Charting these data points reasonably within the available visual real estate implies depicting them one at a time. Therefore, chart displays are linked to the chosen item within the data grid.

To keep the data grid and the chart data synchronized and to accommodate the initial data load and subsequent updating, the data grid code is now:

<mx:DataGrid id="xRateDG" dataProvider="{xRateLatestData}" change="drawChart()"
width="100%" height="50%" />

Here, you will notice four important changes from the original version at the beginning of this section:

  • The result is not bound directly as the data provider anymore. You will notice that XchangeRatePair is not annotated with the [Bindable] metadata.

  • An event handler handles the returned results.

  • On success, a separate currency rates array collection is created for each currency pair. This array collection holds all the data as it changes. This time series data is the underlying data for the charts.

  • The charts are drawn after a successful data fetch.

The result of the remote procedure call is not the dataProvider of the data grid anymore. Instead a variable of an ArrayCollection type called xRateLatestData is bound as the dataProvider.

As soon as the application is ready, the call to the remote procedure is invoked. This hasn't changed from the previous version of the code. How the results are handled has changed though. On success, the result is bound as the data provider of the data grid. The success cases invoke the resultHandler, which is the event listener for the result event. The resultHandler code is:

private function resultHandler(event:ResultEvent):void {
                       xRateLatestData = event.result as ArrayCollection;

                       var xRatePair:XchangeRatePair;

                       for(var i:int; i<xRateLatestData.length; i++) {
                               xRatePair = xRateLatestData.getItemAt(i) as
                                  XchangeRatePair;

xRates[xRatePair.symbol] = new ArrayCollection();
                       }

                       xRateDG.selectedIndex = 0;
                       xRateUpdateConsumer.subscribe();
                       drawChart();

              }

Besides all these changes, a new component called the Consumer is also used. Consumer and Producer are components that are used to receive and send data, respectively. These are the two client-side Flex components that support messaging and play an important role in data push applications. The consumer is this specific case is backed by a destination referenced as xrateupdate. This destination is configured in messaging-config.xml, which is included in services-config.xml by reference. The configuration details are shown in Listing 6-3. Most of the configuration in this example is about the JMS-specific parameters, which will become understandable after you read through the next subsection, which explains the simulated feed client.

Once a message arrives, the messageHandler event listener function is invoked. The messageHandler function does a few things, including the following:

  • Finds out which pair has the update and updates the latest collection for the data grid

  • Adds the updates to the currency rates array collection for the specific pair

  • Sorts the currency rates on the basis of the lastUpdate value

  • Draws the chart to include the update

The messageHandler code is:

private function messageHandler(event:MessageEvent):void {
                       var xRatePair:XchangeRatePair = event.message.body as
                         XchangeRatePair;
                       //var initialXRatePair:XchangeRatePair;
                       for(var i:int; i<xRateLatestData.length; i++) {
                               var initialXRatePair:XchangeRatePair =
                                 xRateLatestData[i];
                               if(initialXRatePair.symbol == xRatePair.symbol) {
                                      initialXRatePair.symbol = xRatePair.symbol;
                                      initialXRatePair.bid = xRatePair.bid;
                                      initialXRatePair.offer = xRatePair.offer;
                                      initialXRatePair.lastUpdated =
                                        xRatePair.lastUpdated;
                               }
                       }
                          xRates[xRatePair.symbol].addItem(xRatePair);
                       var sort:Sort = new Sort();
                sort.fields = [new SortField("lastUpdated", true)];
                   xRates[xRatePair.symbol].sort = sort;
                           xRates[xRatePair.symbol].refresh();
                           drawChart();


              }

Example 6.3. Configuration for the Messaging Destination
<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service"
    class="flex.messaging.services.MessageService">

    <adapters>
        <adapter-definition id="actionscript" class="flex.messaging.services.
          messaging.adapters.ActionScriptAdapter" default="true" />
        <adapter-definition id="jms" class="flex.messaging.services.messaging.
          adapters.JMSAdapter"/>
    </adapters>

    <default-channels>
        <channel ref="my-polling-amf"/>
    </default-channels>

     <destination id="xrateupdate">

        <properties>

          <jms>

                <destination-type>Topic</destination-type>
                <message-type>javax.jms.ObjectMessage</message-type>
                <connection-factory>ConnectionFactory</connection-factory>
                <destination-jndi-name>topic/XchangeRateUpdate
                 </destination-jndi-name>
                <destination-name>XchangeRateUpdate</destination-name>
                <delivery-mode>NON_PERSISTENT</delivery-mode>
                <message-priority>DEFAULT_PRIORITY</message-priority>
                <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
                <transacted-sessions>false</transacted-sessions>
            </jms>
        </properties>

        <channels>
            <channel ref="my-polling-amf"/>
        </channels>

        <adapter ref="jms"/>

    </destination>

</service>

As mentioned before the best way to understand this configuration is to first understand the simulation feed.

6.4.4. The Simulated Feed Program

I mentioned previously that connecting our application to real-world data may not be feasible. So, a simulated feed is used to mimic such a situation and provides a way to demonstrate how updates can be handled. First, look at the code in Listing 6-4, which contains everything for the simulation feed.

Example 6.4. Simulated Market Data Feed
package problazeds.ch06;

import java.util.*;
import javax.jms.*;
import javax.naming.*;

public class XchangeRateSimulatedFeed {

      private static Random random;

      /**
       *
       */
      public XchangeRateSimulatedFeed() {
           // TODO Auto-generated constructor stub
      }

      /**
       * @param args
       */
      public static void main(String[] args) {
           // TODO Auto-generated method stub

          TopicSession pubSession;
          TopicPublisher publisher;
          TopicConnection connection;

          String _providerurl = "jnp://localhost:1099";
          String _ctxtFactory = "org.jnp.interfaces.NamingContextFactory";

          try {
                // Obtain JNDI Context
                Properties p = new Properties();
                p.put(Context.PROVIDER_URL, _providerurl);
                p.put(Context.INITIAL_CONTEXT_FACTORY, _ctxtFactory);
                p.put(Context.SECURITY_PRINCIPAL, "admin");
                p.put(Context.SECURITY_CREDENTIALS , "admin");
                Context context = new InitialContext(p);

                // Lookup a JMS connection factory
                Object tmp = context.lookup("ConnectionFactory");
                TopicConnectionFactory factory = (TopicConnectionFactory) tmp;

                // Create a JMS connection
                connection = factory.createTopicConnection();

                // Create publisher session
                pubSession = connection.createTopicSession(false,
                 Session.AUTO_ACKNOWLEDGE);

// Lookup a JMS topic (If you use JRun, topics are configured in
                  SERVER-INFjrun-resources.xml)
                Topic topic = (Topic) context.lookup("topic/XchangeRateUpdate");

                // Create a publisher and a subscriber
                publisher = pubSession.createPublisher(topic);

                  List xRateInitialData = new XchangeRateDataService().
                   getxRateData();

                  random = new Random();
                  while (true) {

                       Thread.sleep(100);

                  for(int i=0;i<xRateInitialData.size();i++) {
                        ObjectMessage message = pubSession.createObjectMessage();
                        XchangeRatePair xcrp = updatedXRate((XchangeRatePair)
                          xRateInitialData.get(i));
                              message.setObject(xcrp);
                        publisher.publish(message, Message.DEFAULT_DELIVERY_MODE,
                          Message.DEFAULT_PRIORITY, 5 * 60 * 1000);
                        System.out.println(message.toString());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

     private static XchangeRatePair updatedXRate(XchangeRatePair xchangeRatePair) {

            boolean bidOrOffer = random.nextBoolean();
            System.out.println("bidOrOffer " + bidOrOffer);
            boolean positiveOrNegative = random.nextBoolean();
            System.out.println("positiveOrNegative " + positiveOrNegative);

            double bid = xchangeRatePair.getBid();
            double offer = xchangeRatePair.getOffer();

            if(bidOrOffer == true){
                  double bidChange = bid * 0.0009;

                  if(positiveOrNegative == true){
                        bid = bid + bidChange;
                  } else
                  {
                       offer = offer - bidChange;
                  }

                  xchangeRatePair.setBid(bid);

}else
            {
                  double offerChange = offer * 0.0009;

                  if(positiveOrNegative == true){
                        offer = offer + offerChange;
                  } else
                  {
                       offer = offer - offerChange;
                  }

                  xchangeRatePair.setOffer(offer);

            }

            xchangeRatePair.setLastUpdated(new Date());

            return xchangeRatePair;

      }


}

I will dissect the code and point to the most important parts without necessarily walking through every single aspect of the semantics. The first part of the code focuses on the JMS client-specific features of the program. The administered objects are accessed from the JNDI instance accessible at jnp://localhost:1099, in the case of JBoss. A JMS connection and then a session are created.

Within a JMS session a logical connection is established with a JMS topic identified as topic/XchangeRateUpdate. You will notice that, in the configuration in the last section, the topic has the same identifier. This is how a JMS client, like this program, ends up sending the message to a topic that is visible to BlazeDS and its JMS adapter.

The simulation Java program sends an object message. An object message is created by using ObjectMessage message = pubSession.createObjectMessage();. The output of a simple simulation program that creates a small random change to the original value is set as the body of the message object. The simulation itself is encapsulated in a small function: updatedXRate. The details of the implementation in this function are not as important as the fact that it just creates a small random change to the bid and offer values, based on some random choices. You are, of course, free to investigate further and find the logic, if that interests you. In addition, you are free to change it as desired.

That completes bulk of the application. What is still left are the chat window and the order entry screen. I will not explain the order entry screen, although it's there for you in the code download. Let me briefly discuss the chat application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.39.144