Use the Kaazing Gateway Java JMS Client Library

In this procedure, you will learn how to use the Kaazing Gateway Java JMS client library and the supported methods. This topic also includes a simple Java JMS Client example.

Note: For this tutorial, you can use any JMS-compliant message broker. The tutorial uses a publicly available Kaazing WebSocket Gateway and message broker at the URL wss://demos.kaazing.com/jms. If you choose to use a local Kaazing WebSocket Gateway (for download information, see Setting Up the Gateway and Clients), the Gateway is configured to connect to the server on tcp://localhost:61613. You can configure the connect URL in the file GATEWAY_HOME/conf/gateway-config.xml. See About Integrating Kaazing Gateway and JMS-Compliant Message Brokers for more information.

Before You Begin

This procedure is part of Checklist: How to Build Java JMS Clients.

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

To Use the Kaazing Gateway Java JMS Client Library

  1. Review the Kaazing Java JMS Client Tutorial app.

    Before you start, take a look at an app that was built with the Kaazing Java JMS Client API. To see this app in action, download or close the Github repo https://github.com/kaazing/java.client.tutorials and follow the steps for building and running the demo here. You can use a publicly available JMS broker and Gateway at the URL wss://demos.kaazing.com/jms.

  2. Set up your development environment using the Kaazing Enterprise Java Client SDK, as described in Setting Up the Gateway and Clients. If you use Gradle, review the build.gradle file used by the Kaazing Java JMS Tutorial app on Github at https://github.com/kaazing/java.client.tutorials/blob/develop/j2se/java-jms-demo/build.gradle.
  3. Import the required classes. The following example includes Java classes needed for JMS and Kaazing-specific classes for simplifying JMS client development:
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.Properties;
    
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.Destination;
    import javax.jms.ExceptionListener;
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageConsumer;
    import javax.jms.MessageListener;
    import javax.jms.MessageProducer;
    import javax.jms.Session;
    import javax.jms.TextMessage;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    
    import com.kaazing.gateway.jms.client.JmsConnectionFactory;
    import com.kaazing.gateway.jms.client.JmsInitialContext;
    import com.kaazing.net.http.HttpRedirectPolicy;
    import com.kaazing.net.ws.WebSocketFactory;
    
  4. Add the InitialContext.

    Ensure you add the initial context as described in the JNDI documentation, located at http://java.sun.com/products/jndi/tutorial/beyond/env/source.html. The Kaazing Gateway JmsInitialContextFactory returns the initial context. The following example catches exceptions, sets a timeout value, and looks up the ConnectionFactory.

    Properties env = new Properties();
    env.setProperty("java.naming.factory.initial", "com.kaazing.gateway.jms.client.JmsInitialContextFactory");
    try {
     jndiInitialContext = new InitialContext(env);
    } catch (NamingException e1) {
     throw new RuntimeException("Error creating initial context factory for JMS!", e1);
    }
    env.put(JmsInitialContext.CONNECTION_TIMEOUT, "15000");
    try {
     connectionFactory = (ConnectionFactory) jndiInitialContext.lookup("ConnectionFactory");
    } catch (NamingException e) {
     throw new RuntimeException("Error locating connection factory for JMS!", e);
    }
    
  5. Set up the Connection.

    Add a JMS Client implementation of ConnectionFactory that is used to create a connection with a JMS provider via a WebSocket connection. The following example sets up the connection object (JmsConnectionFactory), a default HTTP redirection policy (for more information, see Setting and Overriding Defaults on the WebSocketFactory), creates a session (createSession()), and connects to the broker via the Gateway (connection.start()). Note that there is a lot of exception handling also (ExceptionListener()). This ensures that the client catches exceptions when creating the components of the connection.

    JmsConnectionFactory jmsConnectionFactory = (JmsConnectionFactory) connectionFactory;
    jmsConnectionFactory.setGatewayLocation(url);
    WebSocketFactory webSocketFactory = jmsConnectionFactory.getWebSocketFactory();
    webSocketFactory.setDefaultRedirectPolicy(HttpRedirectPolicy.ALWAYS);
    try {
     connection = connectionFactory.createConnection(login, password);
    } catch (JMSException e) {
     throw new RuntimeException("Error connecting to gateway with " + url.toString() + ", credentials " + login + "/" + password, e);
    }
    try {
     connection.setExceptionListener(new ExceptionListener() {
    
      public void onException(JMSException exception) {
       System.err.println("JMS Exception occurred " + exception.getMessage());
    
      }
     });
    } catch (JMSException e) {
     throw new RuntimeException("Error setting exceptions listener. Connection: " + url.toString() + ", credentials " + login + "/" + password, e);
    }
    try {
     session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    } catch (JMSException e) {
     connection.close();
     throw new RuntimeException("Error creating session. Connection: " + url.toString() + ", credentials " + login + "/" + password, e);
    }
    try {
     connection.start();
    } catch (JMSException e) {
     throw new RuntimeException("Error starting connection: " + url.toString() + ", credentials " + login + "/" + password, e);
    }
    System.out.println("Connected to " + url.toString());
    
  6. Use the Session instance.

    When the connection was created in the previous example, a session was also created.

    session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    

    You will use the session to create consumers for destinations and producers for subscriptions. When you create the consumer, you will also add a MessageListener to receive asynchronously delivered messages.

    Destination subDestination; // create the Destination instance
    try {
      subDestination = (Destination) jndiInitialContext.lookup("/topic/" + subTopicName); // lookup the topic name
    } catch (NamingException e) { // catch exceptions and close if needed
      connection.stop();
      connection.close();
      throw new RuntimeException("Cannot locate subscription topic " + subTopicName, e);
    }
    try {
      consumer = session.createConsumer(subDestination); // create a consumer using the destination
    } catch (JMSException e) { // catch exceptions and close if needed
      session.close();
      connection.stop();
      connection.close();
      throw new RuntimeException("Cannot create consumer for subscription topic " + subTopicName, e);
    }
    System.out.println("Created subscription to " + subTopicName + " for connection to " + url); // notify the user that a subscription was successful
    try {
      consumer.setMessageListener(new MessageListener() { // create a MessageListener
    
        public void onMessage(Message message) { // event handler for incoming messages
          if (message instanceof TextMessage) { // output Text messages
            try {
              System.out.println(">>> MESSAGE RECEIVED: " + ((TextMessage) message).getText());
            } catch (JMSException e) { // catch exceptions
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
          } else {
            System.err.println("Received message of an unexpected type " + message.getClass().getName());
          }
    
        }
      });
    } catch (JMSException e) { // close the session and connection for exceptions
      session.close();
      connection.stop();
      connection.close();
      throw new RuntimeException("Cannot create messages listener for subscription topic " + subTopicName, e);
    
    }
    Destination pubDestination; // create Destination instance for publishing
    try {
      pubDestination = (Destination) jndiInitialContext.lookup("/topic/" + pubTopicName); // lookup publisher name
    } catch (NamingException e) { // close the session and connection for exceptions
      consumer.close();
      session.close();
      connection.stop();
      connection.close();
    
      throw new RuntimeException("Cannot locate publishing topic " + pubTopicName, e);
    }
    try {
      producer = session.createProducer(pubDestination); // create a producer for the destination
    } catch (JMSException e) { // close the session and connection for exceptions
      consumer.close();
      session.close();
      connection.stop();
      connection.close();
      throw new RuntimeException("Cannot create producer for publishing topic " + pubTopicName, e);
    }
    

    Note: When setting up your message consumers and producers, you must use the format "/topic/" for regular topics. For regular queues, use "/queue/". For temporary topics and temporary queues, use "/temp-topic/" or "/temp-queue/".

    This example only sends and receives text messages. You can support binary messages using BytesMessage. Here's an example where a user can select a Binary checkbox and the program responds by sending their message as binary.

    String destinationName = destination.getText(); // get the destination submitted by the user 
    Destination destination = (Destination)jndiInitialContext.lookup(destinationName); // look up destination
    MessageProducer producer = session.createProducer(destination); // create a producer with destination
    
    String text = message.getText();
    try {
      if (binary.isSelected()) { // if the user clicks the Binary checkbox
        logMessage("SEND BytesMessage: " + hexDump(text.getBytes()) + " : " + destinationName);
        BytesMessage bytesMessage = session.createBytesMessage(); // send a message containing a stream of uninterpreted bytes
        bytesMessage.writeUTF(text); // encode the message
        producer.send(bytesMessage); // send the binary message
      }
      else { // handle text messages
        logMessage("SEND TextMessage: " + text + " : " + destinationName);
        TextMessage textMessage = session.createTextMessage(text);
        producer.send(textMessage);
      }
    }
    catch (Exception exception) { // catch exceptions
      handleException(exception);
    }
    producer.close(); // close producer
    

    For more information on the Kaazing Gateway JMS client libraries, see Kaazing Gateway JMS Client Libraries: Supported APIs and the Kaazing Gateway JMS API documentation.

Migrate Java Applications to Kaazing Java JMS SDK 4.x

If you wish to migrate your Kaazing Gateway 3.3-3.5 Java clients to Kaazing Java JMS SDK 4.x and use its new libraries, do the following:

  1. Use the new WebSocket library in your client, as described above.
  2. Use the new Java JMS Client library, as described above.
  3. Update your Java client to use the new class names:
    • JmsConnectionFactory (previously StompConnectionFactory)
    • JmsConnectionProperties (previously StompConnectionProperties)
    • JmsInitialContext (previously StompInitialContext)
    • JmsInitialContextFactory (previously StompInitialContextFactory)
  4. Modify challenge handlers. In Kaazing Java JMS SDK 4.x, the ChallengeHandlers class from 3.3-3.5 was replaced with by the ChallengeHandler modifier of the WebSocketFactory class. The ChallengeHandler modifier is used during authentication for connections and subsequent revalidation that occurs at regular intervals.

    Kaazing Gateway 3.3-3.5:

    import com.kaazing.gateway.client.security.BasicChallengeHandler;
    import com.kaazing.gateway.client.security.ChallengeHandlers;
    import com.kaazing.gateway.client.security.LoginHandler;
    ...
    private void initLoginHandler(String location) {
      final LoginHandler loginHandler = new LoginHandler() {
        private String username;
        private char[] password;
        @Override
        public PasswordAuthentication getCredentials() {
          try {
            LoginDialog dialog = new LoginDialog(Frame.getFrames()[0]);
            if (dialog.isCanceled()) {
              return null;
            }
            username = dialog.getUsername();
            password = dialog.getPassword();
          } catch (Exception e) {
            e.printStackTrace();
          }
          return new PasswordAuthentication(username, password);
        }
      };
      BasicChallengeHandler challengeHandler = ChallengeHandlers.load(BasicChallengeHandler.class);
      challengeHandler.setLoginHandler(loginHandler);
      ChallengeHandlers.setDefault(challengeHandler);
    }
    

    Kaazing Java JMS SDK 4.x:

    import com.kaazing.net.auth.BasicChallengeHandler;
    import com.kaazing.net.auth.ChallengeHandler;
    import com.kaazing.net.auth.LoginHandler;
    ...
    private ChallengeHandler createChallengeHandler(String location) {
      final LoginHandler loginHandler = new LoginHandler() {
        private String username;
        private char[] password;
        @Override
        public PasswordAuthentication getCredentials() {
          try {
            LoginDialog dialog = new LoginDialog(Frame.getFrames()[0]);
            if (dialog.isCanceled()) {
              return null;
            }
            username = dialog.getUsername();
            password = dialog.getPassword();
          } catch (Exception e) {
            e.printStackTrace();
          }
          return new PasswordAuthentication(username, password);
        }
      };
      BasicChallengeHandler challengeHandler = BasicChallengeHandler.create();
      challengeHandler.setLoginHandler(loginHandler);
      return challengeHandler;
    }
    ...
    // initialize the login handler for the target location
    ChallengeHandler challengeHandler = createChallengeHandler(url);
    stompConnectionFactory.setGatewayLocation(new URI(url));
    WebSocketFactory webSocketFactory = stompConnectionFactory.getWebSocketFactory();
    webSocketFactory.setDefaultChallengeHandler(challengeHandler);
    webSocketFactory.setDefaultRedirectPolicy(HttpRedirectPolicy.SAME_DOMAIN);
    ...
    
  5. Review the Java JMS Client API.

Durable Subscribers

Note: Currently, the Gateway does not support durable subscribers with Apache ActiveMQ. You may use durable subscribers with TIBCO EMS or Informatica UM. For more information, see Durable Subscribers.

If your JMS client needs to receive all of the messages published on a topic, including the ones published while the subscriber is inactive because it is not being used or has lost connections (which is common when using mobile devices), create a durable TopicSubscriber using the Session.createDurableSubscriber method.

To unsubscribe from a durable topic, use the Session.unsubscribe method.

The JMS provider retains a separate record of each durable subscription and ensures that all messages from the topic's publishers are retained until they are acknowledged by each durable subscriber or they have expired. Whether messages have been acknowledged is tracked separately for each durable subscriber, and each durable subscriber is identified by the combination of its name and the clientID (if any) set on the Connection. Ensure your application confirms that the clientID (if used) is unique to the user or device, or, if it does not use clientID's, ensure that the durable name is unique to the user or device.

Note: When clientID is specified in creating a connection, the client.durable.name.format property should be defined for the service in GATEWAY_HOME/conf/gateway-config.xml. For more information, see client.durable.name.format.

Example

In the following example code, an application enables users to subscribe to a destination by specifying the destination, message selector expression, and durable subscriber name when subscribing.

// Obtain the destination name value entered by the user 
String destinationName = destination.getText();
// Obtain the message selector conditional expression entered by the user
String selectorText = messageSelector.getText();
// Obtain the unique name of the durable subscription entered by the user
String durableSubscriberName = durableName.getText();
/* Check to see if the durable subscriber name and 
   message selector expression fields contain values */
boolean hasDurableSubscriberName = 
    (durableSubscriberName != null && !durableSubscriberName.isEmpty());
boolean hasSelector = (selectorText != null && !selectorText.isEmpty());
// Output the values entered by the user
logMessage("SUBSCRIBE: " + destinationName + " " + durableSubscriberName + " " + selectorText);

/* Create the durable subscriber to the specific topic destination 
   Perform a JNDI lookup of the destination name entered by the user */
Destination destination = (Destination)jndiInitialContext.lookup(destinationName);
/* If a topic destination entered by the user is found 
   create a durable subscriber */
if (hasDurableSubscriberName && destination instanceof Topic) {
    // Downcast topic from destination
    Topic topic = (Topic)destination;
    // Create the TopicSubscriber
    TopicSubscriber subscriber;
    /* If a message selector expression is entered by the user,
       use that when creating the durable subscriber */ 
    if (hasSelector) {
        subscriber = 
            session.createDurableSubscriber(topic, durableSubscriberName, selectorText, false);
    }
    // Or, create the durable subscriber without the message selector
    else {
        subscriber = session.createDurableSubscriber(topic, durableSubscriberName);
    }
    // Set the message listener to receive messages from the destination
    subscriber.setMessageListener(this);
}
Notes:

Simple Java JMS Client Example

The following code sample provides a simple example of a Java JMS Client. To understand the API calls in the code, review the Kaazing Gateway JMS Client Libraries: Supported APIs and the Kaazing Gateway JMS API documentation.

package com.kaazing.gateway.jms.client.demo;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.kaazing.gateway.jms.client.JmsConnectionFactory;

public class JmsDemo {
  public static void main(String[] args)
  throws NamingException, URISyntaxException, JMSException {
      
    // Add the initial context using the InitialContext constructor.
    Properties props = new Properties();
    props.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "com.kaazing.gateway.jms.client.JmsInitialContextFactory");
    InitialContext ctx = new InitialContext(props);
      
    // Configure the JMS Client implementation of ConnectionFactory.
    ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup("ConnectionFactory");
      
    /* 
    Use JmsConnectionFactory to create a connection 
    with a JMS provider via a WebSocket connection.
    */
    if ( connectionFactory instanceof JmsConnectionFactory ) {
      ((JmsConnectionFactory) connectionFactory)
      .setGatewayLocation(new URI("ws://localhost:8000/jms"));
    }
      
    /*
    Use the createConnection method to create a JMS Connection via WebSocket.
    The username and password specified are used as credentials to authenticate
    with the Gateway.
    In this example we use a password but the password does not have 
    to be a text password. JmsConnectionFactory() can use a token of any kind.
    */
    final Connection connection = connectionFactory.createConnection(null, null);
    connection.setExceptionListener(new ExceptionListener() {
      @Override
      public void onException(JMSException arg0) { arg0.printStackTrace(); }
    });
      
    /*
    Use the createSession method to create a session object. 
    The first parameter ("false") indicates that the session is not transacted.
    Session.AUTO_ACKNOWLEDGE determines that the session automatically acknowledges
    a client's receipt of a message.
    */
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      
    // Use the lookup method of InitialContext to return the name of the topic object.
    Topic topic = (Topic) ctx.lookup("/topic/destination");
      
    // Create a MessageConsumer for the specified destination.
    MessageConsumer consumer = session.createConsumer(topic);
    // Set the session's distinguished message listener. 
    consumer.setMessageListener(new MessageListener() {
      @Override
      /*
      Use the onMessage method of the MessageListener interface
      to receive asynchronously delivered messages.
      */
      public void onMessage(Message message) {
        try {
          String msg = null;
          // Check to see if the message contains a stream of uninterpreted bytes. 
          if ( message instanceof BytesMessage ) {
            BytesMessage bytesMessage = (BytesMessage) message;
            /*
            Construct a string builder to handle the conversion
            from bytes to strings.
            */
            StringBuilder builder = new StringBuilder();
            // Loop through the message and convert datum to a string.
            for (int i = 0; i < (int) bytesMessage.getBodyLength(); i++) {
              builder.append((char) bytesMessage.readByte());
            }
            msg = builder.toString().trim();
          }
          else {
            // Get the text of the message.
            msg = ((TextMessage) message).getText();
          }
          // Print out the message content.
          System.out.println("The received message: " + msg);
        }
        catch (Exception e) { e.printStackTrace();
        }
      }
    });
    // Start (or restart) a connection's delivery of incoming messages.
    connection.start();
      
    // Wait 2 seconds before sending a message.....
    try { Thread.sleep(2 * 1000); }
    catch (InterruptedException e) { e.printStackTrace(); }
      
    /*
    Use a MessageProducer object to send messages to a destination.
    Use the createProducer method to set the destination for messages.
    */
    MessageProducer producer = session.createProducer(topic);
      
    /*
    Create a TextMessage object to send a message containing a String object,
    and send the message using the send method using the MessageProducer's 
    default delivery mode, priority, and time to live.
    */        
    TextMessage txtMsg = session.createTextMessage("Hello, message");
    producer.send(txtMsg);
  }
}

Next Step

Secure Your Java and Android Clients

Notes