Friday, May 3, 2013

JMS on Glassfish with Standalone Client

Most of today’s businesses have a whole range of systems supporting the day-to-day business needs and they may be very different in terms of architecture and technologies they are using. Effective communication between these heterogeneous systems has become an integral part of today’s business. Messaging standards like JMS make it easier to create effective communication solutions across distributed systems to exchange business data or events. In this post I will try to explain what is messaging in general and how we can build asynchronous messaging systems using JMS and Message Driven Beans in Glassfish Application Server.

Messaging

Messaging in simple terms is communication between two parties, a sender and a receiver. That can be as simple as an email sent from one party to another. Contents of that email can be some instructions or simply information and the receiver can take action according to the information received in the message.

Enterprise messaging is pretty similar to that, one system (sender) sends a message to another system (consumer) and the receiver can take appropriate action against that message. It is not mandatory for the receiver to be available at the time when message was sent, nor do they need to know each other in order to exchange messages. All they need is a defined format of the message so that receiver can understand what message is sent by the sender. This loose coupling makes messaging solutions completely different in comparison to other communication solutions like CORBA, or RMI, etc.

JMS Messaging

Java Messaging Service (JMS) is a messaging API for Java platform that defines a common set of interfaces to create, send and receive messages. There are two ways the JMS messaging solution can be implemented. Point-to-point and Publish Subscribe. We will look at both of these models in this post.

Point-to-point
The point-to-point model relies on the concept of MessageQueue. In this model there will be only one sender and one consumer at both ends of the Queue. The sender sends a message to a Queue which is then received by the Consumer from the same Queue. The Consumer the processes the message and acknowledges the receipt of the messages. There is no timing dependency in this model. If the Consumer is not available at the time the message is sent, it will remain in the Queue and the Consumer can receive the message when it becomes available again and acknowledge the receipt of the message. A message can remains in the Queue until it is acknowledged.

Publish Subscriber
This model allows sending a message to multiple Consumers. The message is not sent to any consumer; it is broadcasted to a channel called TOPIC, any Consumers that are SUBSCRIBED to this TOPIC can receive the message. This model does have a timing dependency and the messages are not retained in the JMS provider for a long time, if any subscriber is not active at the time of message broadcast, that subscriber will lose that message. However, the JMS API allows creating a DURABLE SUBSCRIPTION to receive messages if a Subscriber is not active. The JMS Provider will retain a message for a DURABLE SUBSCRIBER until it is received or the message is expired.

JMS on Glassfish

The Glassfish application server has an integrated JMS provider Open MQ providing full messaging support to any applications deployed on Glassfish. Open MQ is a complete message-oriented middleware and JMS implementation.

In Glassfish we create two JMS resources to communicate using JMS messages; Connection Factories and Destination Resources. A Connection Factory is the object used by the Senders to create connections to the JMS Provider, Open MQ in our case. We can have three types of Connection Factories to establish a connection:

  • Queue Connection Factory: This is used to create a Queue Connection to the JMS Provider.
  • Topic Connection Factory: This is used to create a Topic Connection to the JMS Provider.
  • Connection Factory: This is generic factory object that can be used to create Queue as well as Topic connection.
A Destination Resources is the actual channel which is used by the Sender to send messages and the Consumer to receive messages. We have two types of resources:
  • Queue: This is for point-to-point communication.
  • Topic: This is for publish-subscribe communication.
All of these Connections and Resources must be created in the JMS provider. Once the infrastructure is in place we can start communicating on these channels.

Message-Driven Bean

A Message-Driven Bean (MDB) is a special Enterprise Bean that helps to process JMS Messages asynchronously. An MDB acts as a listener for JMS Messages. A JMS client have no direct access to the MDB, instead the JMS Client sends a message to a JMS Resource (Queue or Topic) and at the other end of that resources an MDB listening on that resources will process the message.

In this post I will be creating the JMS Resources in Glassfish and the MDBs that will be listening to these resources and will deploy these MDBs in Glassfish. After that we will create standalone JMS Client and send messages to these MDBs using JMS Connections. We will do that for both a Queue and a Topic.

  • JMS Queue Messaging.

In the first part we will be creating a Queue in Glassfish and then we will create a MDB listening to that Queue. After that we will create a stand-alone client that will send a JMS Message to the Message Queue.

Creating JMS Resources

First thing that we need to do is to create the JMS resources in Glassfish. I find it easiest to use the AS Admin CLI to create the server resources. For that go to the Glassfish installation directory and type in asadmin, this will open the asadmin prompt. Type in the following commands to create the JMS Resources, below is the output from my computer when I created these resources and it is also showing how to open the ASAdmin prompt.


Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\raza.abidi>cd \glassfish\glassfish\bin

C:\glassfish\glassfish\bin>asadmin
Use "exit" to exit and "help" for online help.
asadmin> create-jms-resource --restype javax.jms.Queue TestQueue
Administered object TestQueue created.
Command create-jms-resource executed successfully.
asadmin> create-jms-resource --restype javax.jms.QueueConnectionFactory TestQueueConnectionFactory
Connector resource TestQueueConnectionFactory created.
Command create-jms-resource executed successfully.
asadmin>

The create-jms-resource command will create the resource for you and once the resources are created then you can execute the list-jms-resources command to see the existing resources in your server. Below is the output from list-jms-resources command in my system.


asadmin> list-jms-resources
TestQueue
TestQueueConnectionFactory
Command list-jms-resources executed successfully.
asadmin>

You have just created the JMS Queue and a QueueConnectionFactory in Glassfish. Now we need to create a MDB that will listen to this Queue for any incoming messages.

All you need to do is to create a class that implements MessageListener and override the onMessage method of MessageListener to provide your own implementation. Also you need to use the @MessageDriven annotation that provides the details of which resource the MDB is listening to.


package com.test.ejb.mdb;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * Message-Driven Bean implementation class for: TestMdb
 */
@MessageDriven(
  activationConfig = { 
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), 
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "TestQueue")}, 
  mappedName = "TestQueue")
public class TestQueueMdb implements MessageListener {

 /**
  * @see MessageListener#onMessage(Message)
  */
 public void onMessage(Message message) {
  
  try {
   message.acknowledge();
  } catch (Exception e) {
   e.printStackTrace();
  }
      
  TextMessage txtMessage = (TextMessage) message;

  try {
   System.out.println(txtMessage.getText());
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

In the @MessageDriven annotation you have to provide two activation properties, destinationType and destination. Also the mapped name property is where you will use the name of the resources to which the MDB is listening to.

When this MDB is deployed in the Glassfish server, it will start listening to the TestQueue. As soon as a message arrives in the TestQueue, it will execute the onMessage method of this bean. In this method you can receive the message and process the message according to your requirements. For the simplicity, I am using the TextMessage, but you can use more complex message types exectly in the same way. Here I am simply extracting the text from the TextMessage object and printing it out to the console.

Now we need to create a JMS Client that will send a message to this Queue to see this in action.


package jms;

import java.util.Properties;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;

public class TestJMSQueue {
 
 public static void main(String a[]) throws Exception {
  
  // Commands to create Queue 
  // asadmin --port 4848 create-jms-resource --restype javax.jms.Queue TestQueue
  // asadmin --port 4848 create-jms-resource --restype javax.jms.QueueConnectionFactory TestQueueConnectionFactory
  
  String msg = "Hello from remote JMS Client";
  
  TestJMSQueue test = new TestJMSQueue();
  
  System.out.println("==============================");
  System.out.println("Sending message to Queue");
  System.out.println("==============================");
  System.out.println();
  test.sendMessage2Queue(msg);
  System.out.println();
  System.out.println("==============================");
  System.exit(0);
 }
 
 private void sendMessage2Queue(String msg) throws Exception{
  
  // Provide the details of remote JMS Client
  Properties props = new Properties();
  props.put(Context.PROVIDER_URL, "mq://localhost:7676");
  
  // Create the initial context for remote JMS server
  InitialContext cntxt = new InitialContext(props);
  System.out.println("Context Created");
  
  // JNDI Lookup for QueueConnectionFactory in remote JMS Provider
  QueueConnectionFactory qFactory = (QueueConnectionFactory)cntxt.lookup("TestQueueConnectionFactory");
  
  // Create a Connection from QueueConnectionFactory
  Connection connection = qFactory.createConnection();
  System.out.println("Connection established with JMS Provide ");
  
  // Initialise the communication session 
  Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
  
  // Create the message
  TextMessage message = session.createTextMessage();
  message.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
  message.setText(msg);
  
  // JNDI Lookup for the Queue in remote JMS Provider
  Queue queue = (Queue)cntxt.lookup("TestQueue");
  
  // Create the MessageProducer for this communication 
  // Session on the Queue we have
  MessageProducer mp = session.createProducer(queue);
  
  // Send the message to Queue
  mp.send(message);
  System.out.println("Message Sent: " + msg);
  
  // Make sure all the resources are released 
  mp.close();
  session.close();
  cntxt.close();
  
 }
 
}

JMS Clients use JNDI to lookup for the JMS Resources in the JMS Provider. Notice the properties passed as parameters to the InitialContext, here in the properties we are providing the JMS provider URL with the server name and port on which the JMS Provider is listening for connections. If the JMS Client is running in the same JVM as JMS Provider then there is no need to provide any additional properties to the InitialContext and it should work seamlessly.

The first thing we need to do is to get a QueueConnectionFactory using the JNDI name and create a Connection using the ConnectionFactory. Then we need to initialise a Session on the connection, this is where you specify the transaction and acknowledgeMode for this session, I am not using transaction, so it's false, and the transaction mode is AUTO_ACKNOWLEDGE. Now you can create a message for this session.

Once you have successfully created the JMS Message then you need to send it to a resource. Again you need to use the JNDI lookup to find the Queue. You get the MessageProducer from the Queue and then the MessageProducer can send a message to this Queue.

After sending the message, it is now time to release the resources.

Running the JMS Client Example

Now let’s run this example. First of all make sure that the Glassfish server is running and the ConnectionFactory and Resource are created. For that you can open the asadmin console and type in the list-jms-resources command to see the JMS resources on your Glassfish installation. This is already described above.

In order to run the client successfully you need a few jar files on your classpath.

From your Glassfish lib folder:

  • gf-client.jar
  • javaee.jar
From your Glassfish modules folder:
  • javax.jms.jar
And these files are in imqjmsra.rar archive that you can find in your glassfish\mq\lib directory. You need to manually extract all of these jar files from imqjmsra.rar and place them in the classpath of your JMS Client.
  • fscontext.jar
  • imqbroker.jar
  • imqjmsbridge.jar
  • imqjmsra.jar
  • imqjmx.jar
  • imqstomp.jar

Once you have your classpath setup and the Glassfish is up and running then you can run the client to see the JMS communication in action. Here is the output when I run the client on my machine.


==============================
Sending message to Queue
==============================

Context Created
Connection established with JMS Provide 
Message Sent: Hello from remote JMS Client

==============================

And here is the output on Glassfish console. This will be available on server.log file by default.


[INFO|glassfish3.1.2|Hello from remote JMS Client]

As you can see from the output, the message sent by the Client is consumed by the MDB.

Now let’s see how to broadcast the JMS messages to multiple consumers.

  • JMS Topic Broadcasting.

Creating a Topic is not much different to creating a Queue in Glassfish. Repeting the same procedure that we did earlier, we now create a Topic and a TopicConnectionFactory.

Creating JMS Resources

First thing that we need to do is to create the JMS resources in Glassfish. Open the asadmin prompt as described earlier and type in the following commands to create the JMS Resources, below is the output from my computer when I created these resources.


asadmin> create-jms-resource --restype javax.jms.Topic TestTopic
Administered object TestTopic created.
Command create-jms-resource executed successfully.
asadmin> create-jms-resource --restype javax.jms.TopicConnectionFactory TestTopicConnectionFactory
Connector resource TestTopicConnectionFactory created.
Command create-jms-resource executed successfully.
asadmin>

The create-jms-resource command will create the resource for you and once the resources are created then you can execute the list-jms-resources command to see the existing resources in your server. Below is the output from list-jms-resources command in my system.


asadmin> list-jms-resources
TestQueue
TestTopic
TestQueueConnectionFactory
TestTopicConnectionFactory
Command list-jms-resources executed successfully.
asadmin>

You have just created the JMS Topic and a TopicConnectionFactory in Glassfish. Now we need to create a few MDBs that will subscribe to this Topic for any broadcasted messages.

Just like before, all you need to do is to create a class that implements MessageListener and override the onMessage method of MessageListener to provide your own implementation. Also you need to use the @MessageDriven annotation that provides the details of which resource the MDB is listening to.

Since we are experimenting with the broadcast where there can be multiple listeners, it is better to create two MDBs to illustrate the working of a publish-subscribe paradigm properly. Below is the code for both of the classes, basically both of them are identical really.


package com.test.ejb.mdb;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * Message-Driven Bean implementation class for: TestTopicMdb1
 */
@MessageDriven(
  activationConfig = { 
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"), 
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "TestTopic")}, 
  mappedName = "TestTopic")
public class TestTopicMdb1 implements MessageListener {

 /**
     * @see MessageListener#onMessage(Message)
     */
    public void onMessage(Message message) {
     
     try {
   message.acknowledge();
  } catch (Exception e) {
   e.printStackTrace();
  }
     
     TextMessage txtMessage = (TextMessage) message;
     
     try {
   System.out.println("First Listener: " + txtMessage.getText());
  } catch (Exception e) {
   e.printStackTrace();
  }
    }

}

And the second one is:


package com.test.ejb.mdb;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * Message-Driven Bean implementation class for: TestTopicMdb2
 */
@MessageDriven(
  activationConfig = { 
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"), 
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "TestTopic")}, 
  mappedName = "TestTopic")
public class TestTopicMdb2 implements MessageListener {

 /**
     * @see MessageListener#onMessage(Message)
     */
    public void onMessage(Message message) {
     
     try {
   message.acknowledge();
  } catch (Exception e) {
   e.printStackTrace();
  }
     
     TextMessage txtMessage = (TextMessage) message;
     
     try {
   System.out.println("Second Listener: " + txtMessage.getText());
  } catch (Exception e) {
   e.printStackTrace();
  }
    }

}

In the @MessageDriven annotation you have to provide two activation properties, destinationType and destination. Also the mapped name property is where you will use the name of the resources to which the MDB is listening to.

When this MDBs are deployed in the Glassfish server, they will subscribe to the TestTopic. As soon as a message arrives in the TestTopic, they will execute the onMessage method. Here I am simply extracting the text from the TextMessage object and printing it out to the console.

Now we need to create a JMS Client that will send a message to this Topic to see this in action.


package jms;

import java.util.Properties;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;

public class TestJMSTopic {
 
 public static void main(String a[]) throws Exception {
  
  // Commands to create Topic
  // asadmin --port 4848 create-jms-resource --restype javax.jms.Topic TestTopic
  // asadmin --port 4848 create-jms-resource --restype javax.jms.TopicConnectionFactory TestTopicConnectionFactory
  
  String msg = "Hello from remote JMS Client";
  
  TestJMSTopic test = new TestJMSTopic();
  
  System.out.println("==============================");
  System.out.println("Publishig message to Topic");
  System.out.println("==============================");
  System.out.println();
  test.sendMessage2Topic(msg);
  System.out.println();
  System.out.println("==============================");
  System.exit(0);
 }
 
 
 private void sendMessage2Topic(String msg) throws Exception{
  
  // Provide the details of remote JMS Client
  Properties props = new Properties();
  props.put(Context.PROVIDER_URL, "mq://localhost:7676");
  
  // Create the initial context for remote JMS server
  InitialContext cntxt = new InitialContext(props);
  System.out.println("Context Created");
  
  // JNDI Lookup for TopicConnectionFactory in remote JMS Provider
  TopicConnectionFactory qFactory = (TopicConnectionFactory)cntxt.lookup("TestTopicConnectionFactory");
  
  
  // Create a Connection from TopicConnectionFactory
  Connection connection = qFactory.createConnection();
  System.out.println("Connection established with JMS Provide ");
  
  // Initialise the communication session 
  Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
  
  // Create the message
  TextMessage message = session.createTextMessage();
  message.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
  message.setText(msg);
  
  // JNDI Lookup for the Topic in remote JMS Provider
  Topic topic = (Topic)cntxt.lookup("TestTopic");
  
  // Create the MessageProducer for this communication 
  // Session on the Topic we have
  MessageProducer mp = session.createProducer(topic);
  
  // Broadcast the message to Topic
  mp.send(message);
  System.out.println("Message Sent: " + msg);
  
  // Make sure all the resources are released 
  mp.close();
  session.close();
  cntxt.close();
 }
}

This client is pretty much same as the client we wrote for the communication over JMS MessageQueue. The only difference is that we now get a TopicConnectionFactory using the JNDI name and send it to a Topic. This is how the message is Broadcasted to that Topic.

Running the JMS Client Example

We have the same prerequisites to run this client as we have for the Queue client. Follow the instructions from the Queue client to setup the classpath and make sure the Glassfish is running. Now let’s run this example.

Once you have your classpath setup and the Glassfish is up and running then you can run the client to see the JMS communication in action. Here is the output when I run the client on my machine.


==============================
Publishig message to Topic
==============================

Context Created
Connection established with JMS Provide 
Message Sent: Hello from remote JMS Client

==============================

And here is the output on Glassfish console. This will be available on server.log file by default.


[INFO|glassfish3.1.2|First Listener: Hello from remote JMS Client]

[INFO|glassfish3.1.2|Second Listener: Hello from remote JMS Client]

As you can see from the output on Glassfish server.log that both of the MDBs we created for this excersise got executed and processed the same message in their own way.

This has been an overly simplified example of how MessageQueue and Topic work in Glassfish. I did not take care of the minor details basically to show the concept. I am sure that will give you enough information to get started.

9 comments:

  1. This is a very well explained post on JMS in Glass fish. Great work Raza.

    Jasvinder

    ReplyDelete
  2. Excellent. Thanks, especially to clarify the necessary libraries to run the application. That is something that often fails in most of the examples, which are NOT indicate the necessary libraries to run. Thanks again.

    ReplyDelete
  3. Thanks for this excellent article.

    ReplyDelete
  4. A very Good post.We explained.

    ReplyDelete
  5. Hi your tutorial is excellent, and it is running as a charm... Can you please tell me how to run it remotely on two different machines ? It is running on the same machine but not on two :/

    ReplyDelete
    Replies
    1. Hi Gaynusha,

      Thanks for your comments and apologies for the delayed response. I guess you must have figured that out yourself by now but for the benefit of everyone else I an trying to explain how it works.

      The examples are already communicating to a remote server. Note that the TestJMSQueue and the TestJMSTopic classes are normal java classes with a main method. When you run a class with a main method, it creates its own JVM and will run in its own environment and communication with anything out of that environment will be remote communication, regardless of whether the physical machine is same or something else.

      Notice the line 41 of TestJMSQueue class, here you have a provider URL which is "mq://localhost:7676". There is nothing special about this URL, it will be clearer if we dissect this URL into its parts, this first part is the protocol, i.e. “mq” and then you have the address of the host, i.e. “localhost” and the last part is the port number on which the server is listening to.

      Try running the TestJMSQueue from a different server and simply change the "localhost" on line 41 with the IP address of the server where glassfish is running and listening for the JMS Messages. You may have to add some more jar files to the class path but once all the dependencies are resolved, you should be able to send a message to your remote JMS Server.

      Troubleshooting:
      Most of the problems that you will encounter will be related to communication issues or wrong class path. There are some simple things you can do to avoid these pitfalls

      1. Make sure you have all the necessary jar files there in the class path when you run the client, it will give you something like classnotfound type of errors in such cases.

      2. Also make sure the remote server is visible from your client machine and you can connect to the JMS port. You can use the ping and telnet commands for that, or whatever way you feel comfortable with. If you still find issues then disable the firewalls on both client and server to make sure there is nothing there which is blocking the communication.

      3. Another very common mistake is as simple as typo errors; make sure that the names in all of your lookup commands are exactly the same as in your server when you created those resources. JNDI is case sensitive and will not find the testQueue of the queue is created with the name TestQueue in the server

      Good luck.

      Delete
  6. Hi your tutorial is excellent, but this error is appearing:

    Exception in thread "main" javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial

    Please can you help me?

    ReplyDelete
  7. Hi your tutorial is excellent, but this exception is appearing:

    Exception in thread "main" javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
    at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662) at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662)
    at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
    at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:350)
    at javax.naming.InitialContext.lookup(InitialContext.java:417)
    at control.Main.sendMessage2Queue(Main.java:64)
    at control.Main.main(Main.java:41)
    Java Result: 1

    please can you help me?

    ReplyDelete
    Replies
    1. Hi Giovanni,

      Please double check, and then check again :) These are the telltale signs of something is missing from your classpath. Most probably one of the library from Glassfish is missing in your classpath.

      Delete