In the first post, I wrote JMS basics and how to write a simple JMS client. In this post, I ll delve deeper in to the code and try to reveal the secrets of the magic :)
One can write a JMS client following the three steps below…
1. Setting up the JNDI context
2. Validating the JMS destinations
3. Creating and using the connection for the data exchange
Let us see each step in detail…
Setting up the JNDI context
Java Naming and Directory Interface(JNDI) is a standard implementation-independent API that allows applications to discover and look up data and objects using a “Name”. As a JMS client, I need to understand where to look for the ConnectionFactory and Destinations (topic/queue) that are intermediaries between the producer and consumer for the data exchange. JMS API doesn’t support this. Unlike the connections, sessions, producers, consumers and messages, ConnectionFactory and Destination objects cannot be obtained using JMS API. JNDI comes as a savior and provides a dynamic, portable and configurable mechanism to obtain these objects.
Firstly, we need to create a connection to the JNDI Naming service and obtain the ConnectionFactory and Destination objects from them. JNDI provides a class javax.naming.InitialContext for this purpose. This is the starting point for any JNDI lookup. The properties we put in the InitialContext depends on the JMS directory service we are using.
In the previous post, the initial context was pretty simple…
jndiContext = new InitialContext();
Ideally, we need to put all the required information to connect to the JNDI service. The
Hashtable<String,String> environment = new Hashtable<String,String>();
InitialContext jndiContext = new InitialContext(environment);
Most of the JNDI lookups require all the four properties to be defined in the Initial context. The url is the one where we can locate the registry that contains the directory information. And the “java.naming.factory.initial” is the property that is used to select the service provider as the initial context. It specifies the class name of the initial context factory for the provider. The jar file that contains this class must be loaded into the JMS during the execution. In the above example where we are trying to write a client to OC4J JMS server, we are using “com.evermind.server.ApplicationClientInitialContextFactory” which is a part of oc4j-client.jar. The username and password are the credentials to connect to the JMS. Some of the JMS providers support anonymous security context, while most assume that the credentials can be obtained from the JNDI or current thread.
Validating the JMS destinations
Once the initial context is set, we need to get the JMS connection factory and the destination objects.
connectionFactory = (ConnectionFactory) jndiContext.lookup(“jms/TopicConnectionFactory”);
dest = (Destination) jndiContext.lookup(destName);
The javax.jms.ConnectionFactory is used to create connection object to a JMS server. A ConnectionFactory is a type of administered object, which means that its attributes and behavior are configured by the system administrator responsible for the messaging server. And the connection can created by the ConnectionFactory in the next step which represents a connection to the message server.
The javax.jms.Destination is an interface that encapsulates a provider-specific address. Queues and Topics are two different destinations and they are the administered objects just like the ConnectionFactory. We can get the destination object by looking up the administered objects in the JNDI namespace/registry. To this specific object the consumer would be created.
Creating and using the connection for the data exchange
connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session.createConsumer(dest);
listener = new TextListener();
//delay to read the messages in onMessage()
As mentioned before the connection represents a connection to the JMS server. Every connection created from the ConnectionFactory would be unique. The connection can be managed using start(), stop() and close() methods. Once the start() method is invoked the JMS server will start sending the messages. If we haven’t subscribed to any of the topic, all the messages will be discarded. So its better to subscribe to a destination before starting any connection. A stop() method will stop inbound messages on that connection until the start() is called again and close() method will close the connection with the JMS server.
A connection object is used to create a session object. A Session object is responsible for creating message, consumer and producer objects. To have a good granular control over the consumers, producers and their transactions, we can have multiple sessions created from a connection. We can also create multiple connections to the server to serve the same purpose. But the connections are pretty costly. So it is always better to create multiple sessions than to create multiple connections.
The first parameter of the createSession() method indicates if the session object will be transacted or not. Confused? Let me explain… In JMS, a transaction groups message or a message set in to one single atomic processing unit. Failure of the delivery of a single message may result in the redelivery of the message set. This is the reason why we have to include jta.jar while running a JMS client as it would be expecting some JTA objects. In the above code snippet, the parameter is set to false, which means the Session will not be transacted. The second parameter indicates the acknowledgment mode used by the JMS client. An acknowledgment is nothing but a notification to the message server that the JMS client has received the message. In this case we chose AUTO_ACKNOWLEDGE, which means that the message is automatically acknowledged after it is received by the client.
The session object can also be used to create producer.
producer = session.createProducer(dest);
It is also used to create a message and send it using the producer like below…
TextMessage message = session.createTextMessage();
“dest” is the Topic object which is a handler to the physical Topic on the messaging server. Topic is nothing but some kind of a news group to which a lot of message consumers can subscribe. When a message is published on to the Topic by a publisher, the message is sent to all the subscribers subscribed to the Topic.
Finally, the setMessageListener() method of the consumer object would register a listener to the object. Setting this would invoke the onMessage() method of the “listener” object whenever a JMS server pushes a message to the subscriber. We also need to note that setting a message listener on a consumer object while the object has already registered a message listener is undefined in the JMS specification. So it is better to avoid that situation.
I guess this post would help you in understanding how a JMS client works and would help you in writing one. Happy coding :)