Mobile JMS Examples

This section illustrates two applications that can be realised by combining communicator devices, packet-oriented bearers, and JMS. The data center part of the applications can be hosted by a Wireles Application Service Provider (WASP) or by a wireless portal.

Both applications are very similar in the way they are architected and deployed. They both consist of application logic running in a data center, possibly under control of a J2EE application server. The data center further hosts a scalable JMS message server infrastructure, which is either a standalone JMS product or part of the J2EE application server. The data center also hosts one or more mobile gateways:

click to expand

Trading Platform

The trading platform allows you to view stock quotes in real-time, and to trade in a reliable and secure manner. The system guarantees efficient distribution of stock quotes to many subscribers, using a packet-oriented wireless bearer. The system further ensures reliable exactly-once execution of the transactions issued by the user, such as buying or selling stock. This is accomplished using the store-and-forward mechanism outlined in the programmable device to backend scenario above.

The following screenshot was taken using the Symbian Quartz emulator, and exemplifies what the trading application could look like to the end user:

click to expand

Note 

More details on Symbian applications can be found on the Softwired website - http://www.softwired-inc.com/products/mobile/example.html. Details of running iBus//Mobile on a Symbian emulator can be found in the online documentation provided with the download.

Message Flow

A simple trading application can be built using one JMS topic called quotes for transmitting stock quotes from the data center to the mobile devices, and one message queue called transactions for transmitting purchase orders from the devices to the data center. The following sequence diagram illustrates the interactions occurring between a mobile device and a JMS server application in the data center. The server application is responsible of publishing stock quotes onto the quotes topic and for executing the orders arriving on the transactions queue:

click to expand

Source Code

We will implement a simplified version of the trading application using the iBus//Mobile JMS middleware product from Softwired. This can be downloaded from http://www.softwired-inc.com/downloads/downloads.html. Be sure to download the full JMS server as the mobile extension works as an extension of this. Another useful application is the iBus//MessageServer AdminTool, which can be used for administering topics and queues.

No graphical user interface is provided, which means all relevant information is logged to a command window. Also, the information transmitted as a quote was simplified as well. For example, we provide only one value field, instead of an asking price and a bid value. All the instructions on how to compile and run the example follow the source code listings.

The example consists of three Java classes:

  • TickerClient.Java
    This is a fully functional iBus//Mobile JMS application. It can be run on a PersonalJava-enabled mobile device, such as a Symbian or Windows CE device. It can also be run on a J2SE virtual machine or as an applet. The ticker client uses a UDP communication protocol, optimized for GPRS and UMTS.

  • TickerServer.java
    This is the server-side or backend of the application. The ticker server runs on a server machine, typically in a data center. It uses JMS to interact with the ticker client via the iBus//Mobile gateway.

  • TickerConstants.java
    Constants, which are used by both the client and the server. For instance, queue and topic names.

Features of iBus//Mobile

Before going into the specific aspects of iBus//Mobile, some main product features need to be explained. Although iBus//Mobile implements the standard JMS API for exchanging information between mobile applications and back-end systems, there are product-specific aspects in respect of application initialization, communication protocols, and Quality of Service (QoS). Details are provided in the iBus//Mobile Programmer's Manual that accompanies the download.

One distinguishing feature of iBus//Mobile is its ability to provide the JMS abstractions on top of virtually any wireless bearer. In order to run atop a specific bearer, an appropriate iBus//Mobile protocol stack is used by the mobile application. Protocol stacks are instantiated by protocol loaders. iBus//Mobile ships with a set of protocol loaders for SMS, GPRS, TCP, and HTTP. Developers can implement and use their own stacks and protocols loaders.

Typically, a protocol loader also instantiates protocol objects for adding a certain QoS to the raw bearer. Examples being guaranteed delivery, data compression, and encryption. If guaranteed delivery is required by an application, then protocol objects are loaded that store JMS messages locally on the device. This is to allow a disconnected operation of the device.

Mobile Ticker Client

The mobile JMS ticker client uses a topic (called quotes) for receiving quotes from the backend service in real time. It also uses a queue (called transactions) for transmitting trade requests to the backend:

     package mobile.ticker;     // Implementation of javax.jms.*:     import ch.softwired.mobilejms.*;     // Mobile access to JNDI:     import ch.softwired.mobileibus.naming.NameServer;     // For registering iBus//Mobile protocol loaders:     import ch.softwired.mobileibus.qos.ProtocolStackFactory;     import java.util.Random;     import java.util.Hashtable;     public class TickerClient implements MessageListener {,        private final Random random_ = new Random();        // For issuing trades via a message queue:        private QueueConnection tradeConnection_;        private QueueSession tradeSession_;        private QueueSender tradeSender_;         // For receiving quotes via a topic:        private TopicConnection quoteConnection_; 

When the main() method is executed, the client first goes through an initialization step in which an iBus//Mobile protocol stack loader is specified. This allows a developer to choose a particular communication protocol (for example, TCP, HTTP, or UDP) and a particular Quality of Service (QoS) (for example, reliable sliding window, reliable store-and-forward). The protocol and QoS will be used between the mobile device and the gateway application:

       // Initializes and starts the ticker client:       public static void main(String[] args) throws Exception {          TickerClient client, = new TickerClient();          client.initialize() ;          client.subscribe();          client.start();          System.err.println("Client is ready.");          System.err.println();           // Prevent the main thread from terminating. Otherwise           // the client might just terminate as well.           Thread.currentThread().sleep(60000);       } 

The client will connect to the gateway located on the same host as the client (localhost). To connect to another gateway, the host name of the gateway can be passed to the registerQos() method. This is described in the iBus//Mobile Programmer's Manual Finally, a topic and a queue connection factory is created:

       private void initialize() throws JMSException {          TopicConnectionFactory quoteConnFactory;          QueueConnectionFactory tradeConnFactory;          // Register an iBus//Mobile protocol stack loader.          //This ticker client can run atop virtually any wireless          //bearer or communication protocol. It's a matter of defining          //an appropriate Quality of Service (QoS) loader:          Hashtable qosParams = new Hashtable();          qosParams.put("HOSTNAME", TickerConstants.HOSTNAME);          ProtocolStackFactory.registerQos ("qos-ticker" TickerConstants.QOS_LOADER,     qosParams);          // Create a JMS Topic- and a QueueConnectionFactory for the          // aforementioned QoS loader:          quoteConnFactory = NameServer.lookupTopicConnectionFactory("qos-ticker") ;          tradeConnFactory = NameServer.lookupQueueConnectionFactory("qos-ticker") ;          // Create a JMS Topic- and a QueueConnection:          quoteConnection_ = quoteConnFactory.createTopicConnection();          tradeConnection_ = tradeConnFactory.createQueueConnectiont();       } 

Next, the client application creates a queue sender for the transactions topic, and a topic subscriber for the quotes topic. The topic subscriber is used for receiving quotes published by the backend. The queue sender allows the client to issue a trade to sell or buy a particular item:

       private void subscribe() throws JMSException {          Topic quoteTopic;          TopicSession quoteSession;          TopicSubscriber quoteSubscriber; Queue tradeQueue;          // For receiving quotes from the back-end (see "onMessage()"):          quoteSession = quoteConnection_.createTopicSession(false,             Session.AUTO_ACKNOWLEDGE);          quoteTopic = quoteSession.createTopic(TickerConstants.TOPIC_QUOTES) ;          quoteSubscriber = quoteSession.createSubscriber (quoteTopic)          quoteSubscriber .setMessageListener (this) ;          // For transmitting trades to the back-end:          tradeSession_ = tradeConnection_.createQueueSession(false,             Session.AUTO_ACKNOWLEDGE);          tradeQueue = tradeSession_.createQueue(TickerConstants.QUEUE_TRADES)          tradeSender_ = tradeSession_.createSender(tradeQueue);       } 

When a quote is received, onMessage() is invoked by the iBus//Mobile run-time system. The client then uses a random number to decide whether it wants to issue a trade based on the information received in the quote:

       public void onMessage(Message msg) {          final MapMessage quote = (MapMessage)msg;          int quantity;          String symbol;          String value;          long timeStamp;          // Extract ticker symbol, current value, and time stamp:          try {             symbol = quote.getString(TickerConstants.MAP_KEY_SYMBOL) ;             value = quote.getString(TickerConstants. MAP_KEY_VALUE);             timeStamp = quote.getJMSTimestamp()          } catch (JMSException je) {             System.err.printIn("MapMessage.get<Type> failed: " + jet);             return;          }          // Display the quote:          printQuote(symbol, value, timeStamp);          // Figure out whether we want to issue a trade:          if ((quantity = computeQuantity(symbol, value)) > 0) {             System.out.println(Trading " + quantity + " items of "                + symbol + "@" + value);          // Issue a trade:          try {             doTrade(symbol, value, quantity);          } catch (JMSException je) {             System.err.println("doTrade failed: " + je);             return;          }       } else {             System.out.println("No trade for " + symbol + "@" + value);          }       }       // Starts the JMS connections:       private void start() throws JMSException {          tradeConnection_.start();          quoteConnection_.start();       } 

This illustrative client system uses a random number to decide whether a trade shall be issued for a particular quote:

       private int computeQuantity(String symbol, String value) {          final int tradeThreshold = 400;          final int nextRand = random_.nextInt() % 1000;          if (nextRand < tradeThreshold) {             return nextRand;          } else {            return 0;          }       }       // Sends a trade request to the back-end:       private void doTrade(String symbol, String value, int quantity)           throws JMSException {          MapMessage trade = tradeSession_.createMapMessage();          trade.setString(TickerConstants.MAP_KEY_SYMBOL, symbol);          trade.setString(TickerConstants.MAP_KEY_VALUE, value);          trade.setInt(TickerConstants.MAP_KEY_QUANTITY, quantity);          tradeSender_.send(trade);       }       // Displays a quote:       private void printQuote(String symbol, String value, long timeStamp) {          System. out. pr------");          System.out.println("Got next quote > ");          System.out.println("    Symbol: " + symbol);          System.out.println("    Value: " + value);          System.out.println("    Timestamp: " + timeStamp);          System.out.flush();       }     } 

Back-end Ticker Service

The back-end ticker service is a more conventional JMS application in the sense that it is not run as a mobile application. Whereas the ticker client receives from the quotes topic and sends messages to the transactions queue, the ticker server receives from the transactions queue and publishes quotes to the quotes topic. The class has some of the same structure as the previous one. We start off with a main() method for initialization procedures:

     package mobile.ticker;     import javax.jms.*;     import javax.naming.*;     import java.util .Hashtable;     import java.util.Random;     public class TickerServer implements MessageListener {         private final Random random_ = new Random();         // For receiving trades:        private QueueConnection tradeConnection_;         // For publishing quotes:        private TopicConnection quoteConnection_;        private TopicSession quoteSession_;        private TopicPublisher quotePublisher_;        // Initializes and starts the server application:        public static void main(String[] args)           throws JMSException, NamingException {           TickerServer server = new TickerServer();           server.initialize();           server.subscribe();           server.start();             server.publishQuotes();           System.err.println();       } 

We then proceed to create and configure the destinations:

       private void initialize() throws JMSException, NamingException {          TopicConnectionFactory quoteConnFactory;          QueueConnectionFactory tradeConnFactory;          Context messaging;          Hashtable env = new Hashtable();          // Initilize JNDI context:          env.put(Context.INITIAL_CONTEXT_FACTORY, TickerConstants.JNDI_FACTORY);          messaging = new InitialContext(env);          // Look up connection factories through JNDI:          tradeConnFactory =            (QueueConnectionFactory)messaging.lookup("QueueConnectionFactory");          quoteConnFactory =            (TopicConnectionFactory)messaging.lookup("TopicConnectionFactory");          // Create a JMS Topic- and a QueueConnection:          tradeConnection_ = tradeConnFactory.createQueueConnection();          quoteConnection_ = quoteConnFactory.createTopicConnection();          tradeConnection_.setClientID("tradeConnection");          quoteConnection_.setClientID("quoteConnection");       }       // Issues JMS subscriptions:       private void subscribe() throws JMSException {          Topic quoteTopic;          Queue tradeQueue;          QueueSession tradeSession;          QueueReceiver tradeReceiver;          // For transmitting quotes:          tradeSession =             tradeConnection_.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);          tradeQueue = tradeSession.createQueue(TickerConstants.QUEUE_TRADES);          tradeReceiver = tradeSession.createReceiver(tradeQueue);          tradeReceiver.setMessageListener(this);          // For receiving trades:          quoteSession_ = quoteConnection_.createTopicSession(false,             Session.AUTO_ACKNOWLEDGE);          quoteTopic = quoteSession_.createTopic(TickerConstants.TOPIC_QUOTES);          quotePublisher_ =quoteSession_.createPublisher(quoteTopic);          // Publish quotes in NON_PERSISTENT mode:          quotePublisher_.setDeliveryMode(DeliveryMode.NON_PERSISTENT);       } 

       // Starts the JMS connections. Starts publishing quotes:       private void start() throws JMSException {          tradeConnection_.start();          quoteConnection_.start();       } 

The message handler is then defined. The handler is called by the JMS run-time system when a trade arrives:

       public void onMessage(Message msg) {          MapMessage trade = (MapMessage)msg;          String symbol;          String value;          int quantity;          // Extract ticker symbol, current value, and time stamp:          try {             symbol = trade.getString(TickerConstants.MAP_KEY_SYMBOL);             value = trade.getString(TickerConstants.MAP_KEY_VALUE);             quantity = trade.getInt (TickerConstants.MAP_KEY_QUANTITY) ;          } catch (JMSException je) {             System.err.println("MapMessage.get<Type> failed: " + je) ;             return;          }          printTrade(symbol, value, quantity);       } 

The publishQuotes() method is in charge of publishing a quote every three seconds:

         private void publishQuotes() throws JMSException {             final String SYMBOL = "CSCO";             final double BASE_VALUE = 35.0;             MapMessage quote;             for (;;) {                 final double nextValue = BASE_VALUE + random_.nextDouble();                 quote = quoteSession_.createMapMessage();                 quote.setString(TickerConstants.MAP_KEY_SYMBOL, SYMBOL) ;                 quote.setString(TickerConstants.MAP_KEY_VALUE, "" + nextValue);                 quotePublisher_.publish(quote);                 try { Thread.currentThread().sleep(3000); }                 catch (InterruptedException ie) {}             }         }         // Print a trade:         private void printTrade(String symbol, String value, int quantity) {            System.out.p---");            System.out.println("Got trade request > ");            System.out.println("    Symbol: " + symbol);            System.out.println("    Value: " + value);            System.out.println("    Quantity: " + quantity);            System.out.flush();         }     } 

System Constants

This class stores the constants used in common between client and server:

     package mobile.ticker;     // Constants used by both TickerClient and Tickerserver.     public class TickerConstants {        // Keys used within MapMessages:        public final static String MAP_KEY_SYMBOL    = "Symbol";        public final static String MAP_KEY_VALUE     = "Value";        public final static String MAP_KEY_QUANTITY  = "Quantity";        // iBus//Mobile QoS loader for GPRS (UDP): for iBusMobile 2.0        public final static String QOS_LOADER =           "ch.softwired.mobileibus.qos.QOS_UDP_RELIABLE_SLIWIN" ;        // iBus//Mobile QoS loader for GPRS (UDP): for iBusMobile 1.0        // public final static String QOS_LOADER =        //    " ch.softwired. mobileibus . qos . QOS_DEFAULT " ;        // Hostname of the machine running the server        public final static String HOSTNAME = "localhost";        // Topic name (for the quotes):        public final static String TOPIC_QUOTES = "quotes";        // Queue name (for the trades):        public final static String QUEUE_TRADES = "transactions";        // For JNDI initialization:        public final static String JNDI_FACTORY =           "ch. softwired.jms.naming.IBusContextFactory" ;     } 

The MAP_KEY constants denote the keys that we use for storing and accessing information in JMS map messages. Both the ticker client and the ticker server access these constants.

The QOS_LOADER constant is used only by the client, it denotes the iBus//Mobile protocol stack loader used by the mobile ticker application. This loader is optimized for pushing JMS messages over GPRS and with high throughput and low latency.

TOPIC_QUOTES and QUEUE_TRADES define the topic and the queue name used by ticker client and server.

Finally, JNDI_FACTORY is used by the ticker server application to initialize the JNDI provider.

Running the Ticker System

These are the steps for running the demo locally on a developer workstation. Make sure that you have ibus-mobile.jar and msrvClt.jar in your classpath. They can be found in the \client\j2se\lib\ directory of iBus\\Mobile and the \client\lib\ directory of iBus\\MessageServer respectively:

  • Start the iBus//MessageServer. On Windows, the "Start Server" application is selected through the iBus//MessageServer program group.

  • Start the iBus//Mobile gateway. On Windows, the "Start Gateway" application is selected through the iBus//Mobile program group.

  • Start the ticker server and ticker client as below:

    click to expand

Possible Enhancements

The trading application can be extended in various ways. For example, a confirmation queue can be added, allowing the backend to transmit a confirmation to the mobile client, after a trade request has been processed.

An alerting function is another interesting extension. A mobile client can specify a ticker symbol along with a threshold value. Whenever the quote of the given instrument changes by more then the given threshold value (over a certain period of time), an alert message is pushed to the client.

Buddies

The Buddies application offers functions similar to AOL Instant Messenger or ICQ, but on a mobile device. The user can create a buddy list, containing name and address of friends and family members. When one of the buddies turns his or her communicator on, you will hear a beep from your communicator to indicate a change on your buddy list. The Buddies user interface shows which of your buddies are online and their location. Thus, the server side of the Buddies application requires location information.

Message Flow

Several message flows are necessary to realize the Buddies service. One flow of information is from the device to the backend, to inform the backend when the user logs in or out. This is accomplished through the JMS queue login. There are further flows of messages from the device to the back-end:

  • If the device is capable of determining its own location through a local GPS module, that information is to be transmitted to the backend at regular time intervals. This is accomplished through the JMS queue my location.

  • When the user modifies the buddy list, an update message is sent to the backend through the JMS queue updates.

  • Also, each device sends a heartbeat message to the backend every now and then, using the JMS queue heartbeat. This is to inform the backend that the device is still running. Consider the situation where the Buddies application terminates without publishing a logout message on the login queue. The backend has no way of figuring out that the user is no longer online, unless a heartbeat mechanism is provided.

Finally, there is one message flow from the backend to the device to inform the device of events that relate to its buddy list, like a list member logging in or out. This is where our JMS design becomes a bit tricky: the backend needs to be able to send JMS messages to every single device, individually. This can be accomplished in two different ways:

  • Use one queue per device
    This means the backend can publish a JMS message on queue john_smith to deliver a message only to John Smith's device. This sounds elegant at first, but might result in a huge amount of JMS queues opened by the backend, when our Buddies application becomes very popular. A high-end JMS message server is able to cope with thousands of open JMS destinations, but opening hundred of thousands or millions of destinations concurrently might require a huge amount of resources (notably memory) at the backend.

  • Use a JMS message selector as a filter
    In this design there is exactly one queue called notify for transmitting information from the backend to a device. When the backend wants to transmit a message to John Smith's device, the JMS header property USER_NAME is set to John Smith. The client application uses a JMS selector for that property when subscribing to notify. This solution allows messages to be routed efficiently by the backend JMS infrastructure, provided JMS selectors are evaluated by the JMS provider and not by the lightweight JMS client library.

The diagram below shows the flow of messages. First, the mobile client transmits a JMS message containing login information to the service. If the device is able to determine its location, it will send a first JMS message containing is location to the service.

Next, assume the user of Buddies adds a new contact to a buddy list. An update message is sent to the service. The service will now update the buddy list of the user, and could even notify the contact that they have been added to that list. If nothing else happens, the client application will periodically send its current location to the service as well as a heartbeat message. For optimization, a JMS message could carry both the heartbeat and the location information.

If a contact on the buddy list changes his or her status, a notification message will be sent from the service to the clients who have that contact on their list. In the diagram this would mean an arrow from the service to the client:

click to expand

Possible Enhancements

The Buddies application can be enhanced with features allowing the user to establish chat sessions or voice conferences with their buddies, with instant messaging capabilities, or with a radar-like user interface spotting the geographical location of the buddies.



Professional JMS
Professional JMS
ISBN: 1861004931
EAN: 2147483647
Year: 2000
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net