Use the Kaazing WebSocket Gateway Android JMS Client API

In this procedure, you will learn how to create an Android JMS client using the Kaazing WebSocket Gateway Android JMS Client API. You will learn how to create an Eclipse project and add the necessary Java classes in order to use the Android JMS Client API. You will learn how to implement the Android JMS Client API methods to enable your client to send and receive messages to Apache ActiveMQ through the Gateway.

The Android JMS Client API is nearly identical to the Java JMS Client API. You can review an overview of the Java JMS Client API connection, session, message producer, and message consumer code in Use the Kaazing WebSocket Gateway Java JMS Client Library. For information about using durable subscribers, see Durable Subscribers.

Note: For this procedure, you can use any JMS-compliant message broker. By default, the Gateway is configured to connect to the server on tcp://localhost:61616. You can configure the connect URL in the file GATEWAY_HOME/conf/gateway-config.xml. See About Integrating Kaazing WebSocket Gateway and JMS-Compliant Message Brokers for more information.

In this procedure you will do the following major steps (these step numbers do not correspond to the procedure step numbers directly):

  1. Download the Android ADT Bundle.
  2. Install the Android API.
  3. Set up an Android project in Eclipse.
  4. Import the Kaazing WebSocket Gateway Android and JMS API libraries.
  5. Create the Android client Touch User Interface (TUI).
  6. Add a dispatch queue class to the Android client.
  7. Add the import statements for the common Java and Android classes.
  8. Add the import statements for the Kaazing WebSocket Gateway Android JMS Client API classes.
  9. Add the variables for the program.
  10. Add the onCreate() method with event listeners.
  11. Add the connect() and disconnect() methods.
  12. Add the getDestination() method for creating topics and queues.
  13. Add the ConnectionExceptionListener class to handle when a JMS provider detects an exception.
  14. Add the DestinationMessageListener class and onMessage() method to manage messages.
  15. Add the methods for when the client is paused, resumes, and when the client is closed.
  16. Add the methods for updating the Connect and Disconnect buttons.
  17. Run the Android client in the Android Emulator.
  18. Test the Android client in the Android Emulator.

Before You Begin

This procedure is part of Checklist: How to Build Android JMS Clients Using Kaazing WebSocket Gateway:

  1. Use the Kaazing WebSocket Gateway Android JMS Client API
  2. Secure Your Java and Android JMS Clients
  3. Display Logs for the Android JMS Client

Note: Learn about supported browsers, operating systems, and platform versions in the Release Notes.

Build the Android JMS Messaging Demo

Note: The following procedure uses the Android ADT Bundle (which includes the Eclipse IDE). You can download the Android ADT Bundle from http://developer.android.com/sdk/index.html.

  1. Install the Gateway as described in Setting Up Kaazing WebSocket Gateway. You need the Gateway installed to access the out of the box Android demo files.
  2. Download the Android ADT Bundle from http://developer.android.com/sdk/index.html. For information on using the Android SDK with an existing Eclipse IDE, see http://developer.android.com/sdk/installing/index.html.
  3. To set up the ADT Bundle, see http://developer.android.com/sdk/installing/bundle.html.
  4. Launch Eclipse from the installed ADT Bundle.
  5. Install the Android API.
    1. In Eclipse, click Window, and then click Android SDK Manager.
    2. Select any package from Android 2.3.3 or later and then click Install package.
  6. Set up an Android project in Eclipse.
    1. From the File menu, click New and then Android Application Project.
    2. In Application Name, name the project JMSDemoActivity.
    3. In Package Name, enter com.kaazing.gateway.jms.client.demo.
    4. In Minimum Required SDK, choose API 10: Android 2.3.3 (Gingerbread) and click Next.

      Note: To confirm or modify the SDK requirement on a project, right-click the project and click Properties. Click Android, and then look at the Project Build Target settings.

    5. On New Android Application page, click Next.
    6. On the Configure Launcher Icon page, in Image File, click Browse, navigate to the following location in the Gateway bundle and select the icon.png file:
      GATEWAY_HOME/demo/android/src/jms/res/drawable-hdpi/icon.png
    7. Click Next.
    8. In Create Activity, select Blank Activity and click Next.
    9. In New Blank Activity, in Activity Name, enter JMSDemoActivity.
    10. In Layout Name enter main and click Finish. If there are dependencies that you need to install, the Finish button is not available. The new project is generated.
  7. Import the Kaazing WebSocket Gateway Android and JMS API libraries.
    1. Right-click the new project and click Properties.
    2. In the Properties dialog, click Java Build Path, click the Libraries tab, and click Add External JARs.
    3. Locate and select the Kaazing WebSocket Gateway Android libraries. The libraries are located in GATEWAY_HOME/lib/client/android. The libraries you need are com.kaazing.gateway.client.android.jar and com.kaazing.gateway.jms.client.android.jar.
    4. Click Add External JARs again.
    5. Locate and select the JMS API library. The JMS API library is located in GATEWAY_HOME/lib. The library is named geronimo-jms_1.1_spec-1.1.1.jar.
    6. In the Properties dialog, click OK. You can see the imported library and its classes in your project under Reference Libraries.
  8. Create the Android client Touch User Interface (TUI). Next you will add the text strings and layout for the Android client TUI. When you are finished, the Android client will look like this:
    Figure: Android JMS Client TUI
  9. Open the strings.xml file located at JMSDemoActivity/res/values/strings.xml and replace its contents with the following:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name_short">JMS Demo</string>
        <string name="app_name">JMS Messaging Demo</string>
        <string name="location_label">Location</string>
        <string name="connectBtn_label">Connect</string>
        <string name="disconnectBtn_label">Disconnect</string>
        <string name="destination_label">Destination</string>
        <string name="destination_default">/topic/destination</string>
        <string name="location_default"></string>
        <string name="subscribe_label">Subscribe</string>
        <string name="unsubscribe_label">Unsubscribe</string>
        <string name="message_label">Message</string>
        <string name="message_default">Hello, message</string>
        <string name="send_label">Send</string>
        <string name="clear_label">Clear</string>
        <string name="username_hint">Username</string>
        <string name="password_hint">Password</string>
        <string name="ok_label">OK</string>
        <string name="cancel_label">Cancel</string>
        <string name="sendBinary_label">Send Binary</string>
    </resources>
    

    You can see all of the buttons and values that will be displayed in the TUI. While the location_default field is empty here, you will enter the location into the Android client when you test it.

  10. Open the main.xml file located at JMSDemoActivity/res/layout/main.xml and replace its contents with the following:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/RelativeLayout1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#F27A31">
    
        <LinearLayout
            android:id="@+id/linearLayout1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp" >
    
            <TextView
                android:id="@+id/textView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/location_label"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textSize="@dimen/edit_text_size" />
    
            <EditText
                android:id="@+id/locationText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_weight="1"
                android:ems="10"
                android:text="@string/location_default"
                android:textSize="@dimen/edit_text_size" >
                <requestFocus />
            </EditText>
          
        </LinearLayout>
    
        <Button
            android:id="@+id/disconnectBtn"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/linearLayout1"
            android:layout_marginRight="5dp"
            android:enabled="false"
            android:text="@string/disconnectBtn_label"
            android:textSize="@dimen/edit_text_size" />
    
        <Button
            android:id="@+id/connectBtn"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@+id/disconnectBtn"
            android:layout_alignBottom="@+id/disconnectBtn"
            android:layout_toLeftOf="@+id/disconnectBtn"
            android:text="@string/connectBtn_label"
            android:textSize="@dimen/edit_text_size" />
    
        <LinearLayout
            android:id="@+id/linearLayout2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/connectBtn"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp" >
    
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/destination_label"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textSize="@dimen/edit_text_size" />
    
            <EditText
                android:id="@+id/destinationText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_weight="1"
                android:ems="10"
                android:text="@string/destination_default"
                android:textSize="@dimen/edit_text_size" >
            </EditText>
        </LinearLayout>
    
        <Button
            android:id="@+id/subscribeBtn"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@+id/unsubscribeBtn"
            android:layout_alignBottom="@+id/unsubscribeBtn"
            android:layout_below="@+id/linearLayout2"
            android:layout_toLeftOf="@+id/unsubscribeBtn"
            android:enabled="false"
            android:text="@string/subscribe_label"
            android:textSize="@dimen/edit_text_size" />
        <Button
            android:id="@+id/unsubscribeBtn"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/linearLayout2"
            android:layout_marginRight="5dp"
            android:enabled="false"
            android:text="@string/unsubscribe_label"
            android:textSize="@dimen/edit_text_size" />
    
        <LinearLayout
            android:id="@+id/linearLayout3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/subscribeBtn"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp" >
    
            <TextView
                android:id="@+id/textView3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/message_label"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textSize="@dimen/edit_text_size" />
    
            <EditText
                android:id="@+id/messageText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_weight="1"
                android:ems="10"
                android:text="@string/message_default"
                android:textSize="@dimen/edit_text_size" >
            </EditText>
        </LinearLayout>
    
        <Button
            android:id="@+id/sendBtn"
            style="?android:attr/buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/linearLayout3"
            android:layout_marginRight="5dp"
            android:enabled="false"
            android:text="@string/send_label"
            android:textSize="@dimen/edit_text_size" />
    
        <CheckBox
            android:id="@+id/sendBinaryCheckBox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@+id/sendBtn"
            android:layout_alignParentLeft="true"
            android:layout_below="@id/linearLayout3"
            android:text="@string/sendBinary_label"
            android:textSize="@dimen/edit_text_size" />
    
        <LinearLayout
            android:id="@+id/logContainer"
            android:layout_width="match_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical"
            android:layout_below="@+id/sendBtn">
    
            <TextView
                android:id="@+id/logView"
                android:layout_width="fill_parent"
                android:layout_height="0dp"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_weight="1"
                android:background="@android:drawable/editbox_background"
                android:scrollbars="horizontal|vertical"
                android:textColor="#000000"
                android:maxLines="80"
                android:textSize="@dimen/edit_text_size" />
    
            <Button
                android:id="@+id/clearBtn"
                style="?android:attr/buttonStyleSmall"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:layout_marginRight="5dp"
                android:text="@string/clear_label"
                android:textSize="@dimen/edit_text_size" />
    
        </LinearLayout>
    </RelativeLayout>
    
  11. Add a dispatch queue class to the Android client. A dispatch queue class is used to run tasks in a separate thread from the main thread (to run some tasks asynchronously). The dispatch queue class is used to add Runnable in a queue. Runnable will be run in a first-in first-out basis. All of the blocking calls of the Android client will be run in a background thread so that the TUI is not blocked and can remain responsive.
    1. Right-click the package com.kaazing.gateway.client.demo, click New, and click Class.
    2. In Name enter DispatchQueue and click Finish. The new DispatchQueue.java class is added to the src folder.
    3. Double-click DispatchQueue.java.
    4. Replace the contents with the following code:
      package com.kaazing.gateway.jms.client.demo;
      
      import android.os.Handler;
      import android.os.HandlerThread;
      
      // The class is used to add Runnable in a queue and the runnable added to the queue 
      // will be run in a first in first out basis. This class is useful to run
      // a series of tasks sequentially in a separate thread from the main thread.
      public class DispatchQueue extends HandlerThread {
      
        private Handler handler;
      
        public DispatchQueue(String name) {
          super(name);
        }
      
        // The message blocks until the thread is started. This should be called 
        // after call to start() to ensure the thread is ready.
        public void waitUntilReady() {
          handler = new Handler(getLooper());
        }
      
        // Adds the Runnable to the message queue which will be run on the thread.
        // The runnable will be run in a first in first out basis.
        public void dispatchAsync(Runnable task) {
          handler.post(task);
        }
      
        public void removePendingJobs() {
          handler.removeCallbacksAndMessages(null);
        }
      
      }
      
  12. Modify the main class for the Android client. In the src folder for the project, under com.kaazing.gateway.jms.client.demo, double-click JMSDemoActivity.java. You will add the main Java code for the Android client in this file.
  13. Delete all of the contents except the com.kaazing.gateway.jms.client.demo package declaration and the JMSDemoActivity class declaration:
    package com.kaazing.gateway.jms.client.demo;
    
      // Import statements will go here
    
      public class JMSDemoActivity extends FragmentActivity {
    
        // the remaining code will go here
    
    }
    
  14. Add the import statements for the common Java and Android classes. Add the statements directly after the package com.kaazing.gateway.jms.client.demo package declaration:
    // General classes used for the program
    import java.net.PasswordAuthentication;
    import java.net.URI;
    import java.util.ArrayDeque;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.concurrent.Semaphore;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    // These classes are used for JMS connections and messages
    import javax.jms.BytesMessage;
    import javax.jms.Connection;
    import javax.jms.Destination;
    import javax.jms.ExceptionListener;
    import javax.jms.JMSException;
    import javax.jms.MapMessage;
    import javax.jms.Message;
    import javax.jms.MessageConsumer;
    import javax.jms.MessageListener;
    import javax.jms.MessageProducer;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    
    // These classes are used for the TUI
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    import android.text.method.ScrollingMovementMethod;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.CheckBox;
    import android.widget.EditText;
    import android.widget.TextView;
    
  15. Add the import statements for the Kaazing WebSocket Gateway Android JMS Client API classes.
    // Include these statements with any client
    import com.kaazing.net.ws.WebSocketFactory; // WebSocket
    import com.kaazing.gateway.jms.client.JmsConnectionFactory; // JMS
    import com.kaazing.gateway.jms.client.ConnectionDisconnectedException; // Exceptions
    import com.kaazing.gateway.jms.client.util.Tracer; // Logging
    import com.kaazing.net.http.HttpRedirectPolicy; // Sets HTTP redirect policy
    
    // Include these statements when a client must authenticate with the Gateway
    import com.kaazing.net.auth.BasicChallengeHandler;
    import com.kaazing.net.auth.ChallengeHandler;
    import com.kaazing.net.auth.LoginHandler;
    
  16. Add the variables for the program directly after the JMSDemoActivity class declaration.
    public class JMSDemoActivity extends FragmentActivity {
    
      // Log API for send output to the log
      private static String TAG = "com.kaazing.gateway.jms.client.android.demo";
    
      // Button variables
      private Button connectBtn;
      private Button disconnectBtn;
      private Button subscribeBtn;
      private Button unsubscribeBtn;
      private Button sendBtn;
      private Button clearBtn;
      private CheckBox sendBinaryCheckBox;
      
      // Text variables
      private EditText locationText;
      private EditText destinationText;
      private EditText messageText;
      private TextView logTextView;
    
      // Connection variables
      private JmsConnectionFactory connectionFactory;
      private Connection connection;
      private Session session;
    
      // Dispatch queue
      private DispatchQueue dispatchQueue;
    
      // Hash map holds active consumer instances created with destination string as a key
      private HashMap<String, ArrayDeque<MessageConsumer>> consumers = new HashMap<String, 
        ArrayDeque<MessageConsumer>>();
    

    The hash map uses an array deque for the MessageConsumer object. Array deques hold multiple elements prior to processing (deques) and have no capacity restrictions; they grow as necessary to support usage.

  17. Add the onCreate() method with event listeners.

    The onCreate() method is called when the activity is first created. This is a long method that includes event listeners for user actions in the TUI. The method also contains the JmsConnectionFactory() API call. The JmsConnectionFactory() is the JMS client implementation of ConnectionFactory. JmsConnectionFactory() is used to create a connection with a JMS provider via a WebSocket connection. JmsConnectionFactory() provides the ability to set the Gateway location dynamically.

    @Override
    public void onCreate(Bundle savedInstanceState) {
    
      super.onCreate(savedInstanceState);
      Log.i(TAG, "onCreate");
      setContentView(R.layout.main);
    
      // Create variables for the TUI elements
      connectBtn      = (Button)findViewById(R.id.connectBtn);
      disconnectBtn   = (Button)findViewById(R.id.disconnectBtn);
      subscribeBtn    = (Button)findViewById(R.id.subscribeBtn);
      unsubscribeBtn  = (Button)findViewById(R.id.unsubscribeBtn);
      sendBtn         = (Button)findViewById(R.id.sendBtn);
      clearBtn        = (Button)findViewById(R.id.clearBtn);
      sendBinaryCheckBox = (CheckBox)findViewById(R.id.sendBinaryCheckBox);
    
      locationText    = (EditText)findViewById(R.id.locationText);
      destinationText = (EditText)findViewById(R.id.destinationText);
      messageText = (EditText)findViewById(R.id.messageText);
    
      logTextView     = (TextView)findViewById(R.id.logView);
      logTextView.setMovementMethod(new ScrollingMovementMethod());
    
      // Create the JMS connection factory and the underlying WebSocket factory
      // to create and establish WebSocket connection to send and receive 
      // JMS data over a WebSocket connection.
      if (connectionFactory == null) {
        try {
          connectionFactory = JmsConnectionFactory.createConnectionFactory();
          WebSocketFactory webSocketFactory = connectionFactory.getWebSocketFactory();
          
          // setDefaultFollowRedirect() sets the default HttpRedirectPolicy that is
          // inherited by all the WebSockets created using this factory instance.
          webSocketFactory.setDefaultFollowRedirect(HttpRedirectPolicy.SAME_DOMAIN);
        } catch (JMSException e) {
          e.printStackTrace();
          logMessage("EXCEPTION: " + e.getMessage());
        }
      }
    
      // Run when Connect button is clicked
      connectBtn.setOnClickListener(new OnClickListener() {           
        public void onClick(View v) {
          connectBtn.setEnabled(false);
          dispatchQueue = new DispatchQueue("DispatchQueue");
          dispatchQueue.start();
          dispatchQueue.waitUntilReady();
    
          // Call the connect() method defined later in this code
          connect();
        }            
      });
      
      // Run when Disconnect button is clicked
      disconnectBtn.setOnClickListener(new OnClickListener() {            
        public void onClick(View v) {
          // Call the disconnect() method defined later in this code
          disconnect();
        }
      });
    
      // Run when Subscribe button is clicked
      subscribeBtn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
          // Get the destination name entered by the user
          final String destinationName = destinationText.getText().toString();
          logMessage("SUBSCRIBE - " + destinationName);
          dispatchQueue.dispatchAsync(new Runnable() {
            public void run() {
              try {
                Destination destination = getDestination(destinationName);
                if (destination == null) {
                  return;
                }
                
                // Create a consumer using the destination
                MessageConsumer consumer = session.createConsumer(destination);
                
                // Create an array deque mapping consumers to the destination
                ArrayDeque<MessageConsumer> consumersToDestination = 
                  consumers.get(destinationName);
                  // If there is no consumers to destination map, add this new map
                  if (consumersToDestination == null) {
                    consumersToDestination = new ArrayDeque<MessageConsumer>();
                      consumers.put(destinationName, consumersToDestination);
                    }
                    // If there is a map, update it with the new consumer 
                    consumersToDestination.add(consumer);
                    // Set a message listener in the DestinationMessageListener() method
                    consumer.setMessageListener(new DestinationMessageListener());
              } catch (JMSException e) {
                e.printStackTrace();
                logMessage("EXCEPTION: " + e.getMessage());
              }
            }
          });
        }
      });
    
      // Run when Unsubscribe button is clicked
      unsubscribeBtn.setOnClickListener(new OnClickListener() {        
        public void onClick(View v) {
          // Clear the queue created in subscribeBtn.setOnClickListener() 
          String destinationName = destinationText.getText().toString();
          logMessage("UNSUBSCRIBE - " + destinationName);
          ArrayDeque<MessageConsumer> consumersToDestination = consumers.get(destinationName);
          if (consumersToDestination == null) {
            return;
          }
          // Make MessageConsumer equal to the first element in the array deque 
          final MessageConsumer consumer = consumersToDestination.poll();
          if (consumer == null) {
            return;
          }
          dispatchQueue.dispatchAsync(new Runnable() {
            public void run() {
              try {
                // Close the consumer
                consumer.close();
              } catch (JMSException e) {
                e.printStackTrace();
                logMessage(e.getMessage());
              }
            }
          });
        }
      });
    
      // Run when Send button is clicked
      sendBtn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
          // Get the message text
          final boolean sendBinary = sendBinaryCheckBox.isChecked();
          final String message = messageText.getText().toString();
          logMessage("SEND: " + message);
          dispatchQueue.dispatchAsync(new Runnable() {            
            public void run() {
              try {
                // Get the destination for the message
                String destinationName = destinationText.getText().toString();
                // Create a MessageProducer to send messages to the destination
                MessageProducer producer = 
                  session.createProducer(getDestination(destinationName));
                // Add the message
                Message message;
                // Determine if the message is binary or text
                if (sendBinary) {
                  BytesMessage bytesMessage = session.createBytesMessage();
                  bytesMessage.writeUTF(text);
                  message = bytesMessage;
                }
                else {
                  message = session.createTextMessage(text);
                }
    
                // Send the message
                producer.send(message);
                // Close the producer
                producer.close();
              } catch (JMSException e) {
                e.printStackTrace();
                logMessage(e.getMessage());
              }
            }
          });
        }
      });
    
      // Run when Clear button is clicked
      clearBtn.setOnClickListener(new OnClickListener() {        
        public void onClick(View v) {
          // Clear the log
          logTextView.setText("");
        }
      });
    }
    
  18. Add the connect() and disconnect() methods for connecting to the Gateway and updating the TUI.
    private void connect() {
      logMessage("CONNECTING");
    
      // Since createConnection() is a blocking method that does not return until 
      // the connection is established or fails, it is a good practice to 
      // establish the connection on a separate thread and prevent the TUI from being blocked.
      dispatchQueue.dispatchAsync(new Runnable() {
        public void run() {
          try {
            // Get the location entered by the user
            String location = locationText.getText().toString();
            // Set the target Gateway location
            connectionFactory.setGatewayLocation(URI.create(location));
            // Create the connection to the Gateway
            connection = connectionFactory.createConnection();
            // Connect to the Gateway
            connection.start();
    
            // Create a Session object and set the client to 
            // acknowledge any messages it receives automatically
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            logMessage("CONNECTED");
            // Set an exception listener for the client 
            connection.setExceptionListener(new ConnectionExceptionListener());    
            // Update the TUI 
            updateButtonsForConnected();
          } catch (Exception e) {
            updateButtonsForDisconnected();
            e.printStackTrace();
            logMessage("EXCEPTION: " + e.getMessage());
          }
        }
      });    
    }
    
    private void disconnect() {
      logMessage("DISCONNECTING");
    
      // Clear the queue of any pending messages
      dispatchQueue.removePendingJobs();
      dispatchQueue.quit();
      new Thread(new Runnable() {
        public void run() {
          try {
            // Close the connection
            connection.close();
            logMessage("DISCONNECTED");
          } catch (JMSException e) {
            e.printStackTrace();
            logMessage("EXCEPTION: " + e.getMessage());
          }
          finally {
            connection = null;
            // Update the TUI with the connection status
            updateButtonsForDisconnected();
          }
        }
      }).start();
    }
    

    For more information on sessions and exception listeners, see the Java Connection interface.

  19. Add the getDestination() method for creating topics and queues.
    private Destination getDestination(String destinationName) throws JMSException {
      Destination destination;
      // Create the topic using the destination name entered by the user
      if (destinationName.startsWith("/topic/")) {
        destination = session.createTopic(destinationName);
      }
      // Create the queue using the destination name entered by the user
      else if (destinationName.startsWith("/queue/")) {
        destination = session.createQueue(destinationName);
      }
      else {
        logMessage("Invalid destination name: " + destinationName);
        return null;
      }
      return destination;
    }
    
    Notes:
    • When setting up your message consumers and producers, you must use the format "/topic/" for regular topics.
    • The Kaazing WebSocket Gateway Java JMS Client API supports the Java Naming and Directory Interface (JNDI), but the Kaazing WebSocket Gateway Android JMS Client API does not.
  20. Add the ConnectionExceptionListener class to handle when a JMS provider detects a serious problem with a connection object.
    private class ConnectionExceptionListener implements ExceptionListener {
    
      public void onException(final JMSException exception) {
        logMessage(exception.getMessage());
        if (exception instanceof ConnectionDisconnectedException) {
          updateButtonsForDisconnected();
        }
      }
    }
    

    For more information, see the Java ExceptionListener interface.

  21. Add the DestinationMessageListener class and onMessage() method to manage messages. For more information, see the Java MessageListener interface.
    private class DestinationMessageListener implements MessageListener {
    
      // Run when a message is received
      public void onMessage(Message message) {
        // This try block is used to handle both text and binary messages
        try {
          if (message instanceof TextMessage) {
            logMessage("RECEIVED TextMessage: " + ((TextMessage)message).getText());
          }
          else if (message instanceof BytesMessage) {
            BytesMessage bytesMessage = (BytesMessage)message;
            long len = bytesMessage.getBodyLength();
            byte b[] = new byte[(int)len];
            // Read a portion of the bytes message stream equal to its length
            bytesMessage.readBytes(b);
            // Send the bytes to the hexDump() method and display the string returned
            logMessage("RECEIVED BytesMessage: " + hexDump(b));
          }
    
          // This condition provides support for the Java MapMessage interface.
          // This client receives MapMessage messages, but it does not send them.
          // Review the Android JMS API for information on reading MapMessage messages.
          else if (message instanceof MapMessage) {
            MapMessage mapMessage = (MapMessage)message;
            Enumeration mapNames = mapMessage.getMapNames();
            while (mapNames.hasMoreElements()) {
              String key = (String)mapNames.nextElement();
              Object value = mapMessage.getObject(key);
    
              if (value == null) {
                logMessage(key + ": null");
              } else if (value instanceof byte[]) {
                byte[] arr = (byte[])value;
                StringBuilder s = new StringBuilder();
                s.append("[");
                for (int i = 0; i < arr.length; i++) {
                  if (i > 0) {
                    s.append(",");
                  }
                  s.append(arr[i]);
                }
                s.append("]");
                logMessage(key + ": "+ s.toString() + " (Byte[])");
              } else {
                logMessage(key + ": " + value.toString() + 
                  " (" + value.getClass().getSimpleName() + ")");
              }
            }
            logMessage("RECEIVED MapMessage: ");
          }
          else {
            logMessage("UNKNOWN MESSAGE TYPE: "+message.getClass().getSimpleName());
          }
        }
        catch (Exception ex) {
          ex.printStackTrace();
          logMessage("EXCEPTION: " + ex.getMessage());
        }
      }
    
      // Return a string representation of the bytes received
      private String hexDump(byte[] b) {
        if (b.length == 0) {
          return "empty";
        }
        StringBuilder out = new StringBuilder();
        for (int i=0; i < b.length; i++) {
          out.append(Integer.toHexString(b[i])).append(' ');
        }
        return out.toString();
      }
    }
    

    For more information on byte streams, see the Java BytesMessage interface. For more information on MapMessage, see the Java MapMessage interface.

  22. Add the methods for when the client is paused, resumes, and when the client is closed.
    // Run when the client is paused
    public void onPause() {
      if (connection != null) {
        // Stop the connection in a separate thread
        dispatchQueue.dispatchAsync(new Runnable() {    
          @Override
          public void run() {
            try {
              connection.stop();
            } catch (JMSException e) {
              e.printStackTrace();
            }
          }
        });
      }
      super.onPause();
    }
    
    // Restart the connection when the client resumes activity
    public void onResume() {
      if (connection != null) {
        dispatchQueue.dispatchAsync(new Runnable() {    
          @Override
          public void run() {
            try {
              connection.start();
            } catch (JMSException e) {
              e.printStackTrace();
            }
          }
        });
      }
      super.onResume();
      }
    
    // Disconnect with the client is shutdown 
    public void onDestroy() {
      if (connection != null) {
        disconnect();
      }
      super.onDestroy();
    }
    
  23. Add the methods for updating the Connect and Disconnect buttons according to the state of the WebSocket connection.
    // Run when the client is connected
    private void updateButtonsForConnected() {
      runOnUiThread(new Runnable() {
        public void run() {
          connectBtn.setEnabled(false);
          disconnectBtn.setEnabled(true);
          subscribeBtn.setEnabled(true);
          unsubscribeBtn.setEnabled(true);
          sendBtn.setEnabled(true);
        }
      });
    }
    
    // Run when the client is disconnected    
    private void updateButtonsForDisconnected() {
      runOnUiThread(new Runnable() {
        public void run() {
          connectBtn.setEnabled(true);
          disconnectBtn.setEnabled(false);
          subscribeBtn.setEnabled(false);
          sendBtn.setEnabled(false);
          unsubscribeBtn.setEnabled(false);
        }
      });
    }
    
  24. Add the logMessage() method for the Log area displayed in the client. This method keeps the log to a 100 line maximum.
    private void logMessage(final String message) {
      runOnUiThread(new Runnable() {
        public void run() {
          // Clear log after 100 messages
          if (logTextView.getLineCount() > 100) {
            logTextView.setText(message);
          }
          else {
            logTextView.setText(message + "\n" + logTextView.getText());
          }
        }
      });
    }
    
  25. Update the jms service on the Gateway configuration file to accept on your local IP address.
    1. Open the Gateway configuration file located at GATEWAY_HOME/conf/gateway-config.xml.
    2. Modify the jms service to accept connections containing your local IP address (replace local-ip-address with your IP address):
      <service>
        <name>JMS</name>
        <description>JMS service to JMS server</description>
        
        <accept>ws://${gateway.hostname}:${gateway.extras.port}/jms</accept>
        <accept>ws://local-ip-address:${gateway.extras.port}/jms</accept>
        <type>jms</type>
      
        <properties>
          <connection.factory.name>ConnectionFactory</connection.factory.name>
          <context.lookup.topic.format>dynamicTopics/%s</context.lookup.topic.format>
          <context.lookup.queue.format>dynamicQueues/%s</context.lookup.queue.format>
          <env.java.naming.factory.initial>
            org.apache.activemq.jndi.ActiveMQInitialContextFactory
          </env.java.naming.factory.initial>
          <env.java.naming.provider.url>
            tcp://localhost:61616
          </env.java.naming.provider.url>
        </properties>
      
      <realm-name>demo</realm-name>
      
        <cross-site-constraint>
          <allow-origin>${gateway.hostname}:${gateway.extras.port}</allow-origin>
        </cross-site-constraint>
      </service>
      
    3. Save the file, then restart the Gateway by following the steps in Setting Up Kaazing WebSocket Gateway.
  26. Run the Android client in the Android Emulator. In Eclipse, right-click the JMSDemoActivity project in Package Explorer, select Run As, and then click Android Application. The Android emulator launches.
    Figure: Android JMS Client in the Android Emulator
  27. Test the Android client in the Android Emulator.
    1. In the Location field, enter ws://local-ip-address:8001/jms using your local IP address. For example, ws://192.168.0.103:8001/jms.
    2. Click Connect. The messages CONNECTING and then CONNECTED appear. The WebSocket connection to the Gateway and Apache ActiveMQ was successful.
    3. Click Subscribe. The message SUBSCRIBE - /topic/destination appears. You have subscribed to the destination.
    4. Click Send. The following messages appear:
      RECEIVED TextMessage: Hello, message
      SEND: Hello, message

      You sent a message Hello, message to the destination and received the same message because you are subscribed to that destination.

    5. Click the Send Binary checkbox and click Send again. The following messages appear:
      SEND BytesMessage: Hello, message
      RECEIVED BytesMessage: 0,14,72,101,108,108,111,44,32,109,101,115,115,97,103,101
    6. Click Disconnect to end the session.

Next Steps

Display Logs for the Android JMS Client

TOP