< Day Day Up > |
The next topic related to distributed computing is the Java Messaging Service (JMS), designed by the Java architects to perform message-oriented distributed applications. The message-oriented applications are asynchronous in nature, as they do not wait for (or do not expect) an acknowledgement from the message recipient. Then where does the message go and how is it handled in between? There is a piece of software known as middleware, which resides between the client and the server applications. The client and server never talk directly; rather, the entire communication is handled by the middleware, and this middleware is capable of handling messages directed to different destinations. This is done by the middleware typically by creating and maintaining one or more queues, known as message queues. A queue is a data structure that handles messages in FIFO (first in, first out) order, which means the message received by the queue first in the sequence will be delivered first to the recipient. Examples of such middleware are many, such as IBM MQ Series (very familiar to the UNIX and IBM mainframe developer community), Microsoft Message Queuing service on the Windows platform, Sonic MQ messaging server, and so on. Because these middleware systems provide asynchronous message distribution, they are known as message-oriented middleware (MOM) in the industry. The distinguishing feature of the message-oriented distributed applications is that because the communication between the client and server is facilitated by the middleware, the client and server may be running in different environments. For example, a C++ client on a Windows operating system would be able to work with a Java-based (or specifically J2EE-based) server running on a Linux-based system. What is necessary is that the middleware should be able to support both the platforms and provide an API for that platform. The client and server will then use the appropriate API to send messages in the format acceptable by the middleware. The message-oriented distributed application concepts have been used in the industry for a long time; in fact, it is one of the ancient (still powerful) mechanisms of asynchronous message distribution. The advent of modern distributed application concepts such as Microsoft DCOM, CORBA, and the latest J2EE and Microsoft .NET framework has been successful in bringing newer concepts into existence—such as online (or synchronous) communication between clients and servers—and have created the impression in the minds of many professionals that the asynchronous architectures such as MOM do not have a place in modern application development. This is a total misconception. The existence of asynchronous architectures is not determined or governed by the advent of synchronous mode architectures, however excellent they are. The existence is governed by the business need. There is always a business need for asynchronous mode communication. In fact, a successful and high-performance enterprise application uses fewer resources during peak business hours and should be able to perform asynchronous business operations during off-peak hours (or sometimes even during peak hours). In addition, most businesses do need support to conduct business during off-hours. For example, when we deposit a check in our bank account or withdraw cash from the ATM(particularly during off-hours), we are essentially triggering a number of asynchronous transactions. Consider what happens when we withdraw money from a cash station in a shopping mall. How is it being debited from our account? The bank that maintains the ATM in the shopping mall and the bank where we maintain our account do not necessarily have to be the same; in fact, they are often different banks. However, a number of asynchronous processes between the two banks (or from any bank to any other bank) work behind the scenes without our knowledge to set up necessary transactions that ensure accuracy and timeliness as needed by the business process. The banking example is cited here because most banking systems use many asynchronous processes. These systems wake up when their customers start sleeping. Therefore, the advent of modern technologies did not reduce the importance of asynchronous mode software such as MOM; rather, it enhanced the usability of asynchronous mode communication channels, using the new tools and techniques of the modern era.
There are two types of message-oriented applications. In the first type, the message sender publishes the message for which interested recipients would subscribe. In other words, whoever subscribes to the service will receive the message; hence this type of applications are known as publish-subscribe type. In the second type of applications, the sender specifies one destination as the recipient, and hence this is known as point-to-point type. Whichever type is used, the MOM ensures delivery of the messages to the recipient, as the messages are stored to persistent disk storage. In the case of the point-to-point type message communication, this persistent storage is a queue maintained and managed by the MOM, and in the case of the publisher-subscriber type message delivery system, it is known as a topic, to which the subscribers subscribe or register. If the recipient is not ready to receive the messages, the messages will remain in the queue until the recipient is ready. Once the recipient is ready and starts receiving the messages, it acknowledges the sender for each of the received messages. The second message is sent by the queue administrator (which is MOM) only after successful acknowledgement of the first message.
Having said that much about the need of asynchronous mode systems, and having provided a brief overview of what a MOM can do for distributed computing, it is now time to focus our attention toward understanding the concepts behind this architecture and build an example to see how it works (or to get a feeling that it works). Because we are currently focusing on Java-based messaging services, also known as JMS, we will look at a JMS-based example. JMS is one of the components distributed as part of the J2EE (Java 2 Enterprise Edition) architecture and is an integral part of J2EE API from the 1.3 Release onward. JMS was available as a separate API for releases prior to J2EE 1.3. The example application in this section will be built using the point-to-point mode communication and is developed to run on J2EE Releases 1.3.1 and later.
The steps involved in building the example are listed here.
Because JMS is part of the J2EE framework, make sure that the J2EE_HOME environment variable is set to point to the base directory where J2EE SDK is installed, and the CLASSPATH environment variable contains the jar files in the $J2EE_HOME/lib directory. Also make sure that the PATH environment contains the $J2EE_HOME/bin directory.
Because JMS-based applications need a middleware to work with, we will use the J2EE reference implementation provided by Sun Microsystems to demonstrate the application. The reference implementation is bundled with a set of tools such as the j2ee program, which works as a J2EE server, and the j2eeadmin program to perform administrative operations. The reference implementation is useful and is designed for testing in applications in a development environment, and therefore the programs mentioned here are simple command-line tools.
In the context of JMS applications, the two entities involved in the exchange of messages are known as sender and receiver, and therefore the concept of client and server is diluted. For the sender and receiver to communicate successfully in asynchronous mode, we should create a message queue using the j2eeadmin tool, and the j2ee server will manage this queue. The following command is used to create a queue with a specific name.
$ j2eeadmin –addJmsDestination <queue name> queue
The command option -addJmsDestination indicates adding a JMS destination, which is a queue in this case, as specified in the last argument. The queue name can be any name without spaces. In the example, a queue with the name SatyaQueue is created.
Start the j2ee server with the following command.
$ j2ee –verbose
The –verbose option is not necessary, but is helpful for seeing the messages thrown to the console by the server startup program. When the server is started, the readers might notice messages related to each of the services started by the server, which include the naming service, messaging service, Web service, secured Web service, messages related to the ports at which these services are listening, messages related to binding the data sources, and so on.
The sender and recipient applications are built using the source code presented in Listing 7.21 and Listing 7.22, respectively. It should be noted here that a typical application might function as both the sender and recipient, in which case the functionality to send the messages to the queue and receive from the queue might have to be implemented in the same program. For the sake of simplicity, the sender and recipient features are separated into two programs.
Both the sender and recipient have very similar code up to the point of establishing a connection with the message queue. The name of the message created in the earlier step should be used in the sender and the recipient programs in order to establish connection with the message queue. A JNDI (Java Naming and Directory Interface) context is used to look for the resource (in this case, the queue with the specific name). When the queue is created using the j2eeadmin program, it is registered with the naming service, and thus the program will be able to look for the specific name. The returned resource should be cast to the Queue interface type, as we are working with a queue. Using the JNDI context, we also look for the JMS driver, which is the QueueConnectionFactory object, which is used to create a Connection object. The Connection object is used to create a Session object. It is this Session object that enables the sender and recipient to establish a communication Session. Methods of the Session interface facilitate in creating the necessary message type objects (explained below) and in creating a sender or recipient object.
While creating the Session object, we pass two input arguments to the createSession() method of the Connection object. The first argument is a Boolean variable indicating whether the session participates in transaction, and the second argument is a constant indicating whether the message recipient will acknowledge the receipt of the message. In this example, we are indicating that the session will not participate in transaction by passing false as the first argument, and the acknowledgement mode is Session.AUTO_ACKNOWLEDGE, indicating that the recipient of the message automatically sends an acknowledgement.
The javax.jms package defines the necessary classes and interfaces for using the JMS features. There are different types of messages that can be sent using JMS API. For example, a TextMessage type message is used to send a string of characters or text in the message, an ObjectMessage type message is used to send any serializable Java object, a StreamMessage type message is used to send a stream of Java primitive data types such as int, char, double, and so on. The CreateTextMessage() method on the Session interface returns a text message type object, while the CreateObjectMessage() method returns an object message type object, and so on. The example demonstrates sending a text message and an object message. Each of these message types is an interface derived from the Message interface and contains a set method to set the appropriate object type, and a get method to retrieve the specific object type. The set method is used by the sender program, and the get method is used by the recipient program.
Compile the programs using the javac compiler like any other Java program.
While running the programs, we need to supply the jms.properties setting to the Java Virtual Machine. The J2EE framework provides a default JMS property file, which can be used in the example. The file is located in the $J2EE_HOME/config directory and is named as jms_client.properties. The following two commands run the sender and recipient programs, respectively. The sender program is invoked with two command-line arguments the name of the queue and the name of the person executing the program. The sender program will send the name of the person in the first message as a text message type and will send a java.util.Calendar object in the second message.
$ java – Djms.properties=$J2EE_HOME/config/jms_client.properties JmsMsgSender SatyaQueue Satya $ java – Djms.properties=$J2EE_HOME/config/jms_client.properties JmsMsgRecipient SatyaQueue
The recipient program is designed to receive both types of messages. At this time, it is important to note that an object message type can be used to send any Java object, and therefore, it is needed to cast to the appropriate object type.
If the queue is not needed anymore, it is removed using the following command. The input argument to this command line is the name of the queue, which is SatyaQueue in the current example. Once the queue is removed, neither the sender nor the recipient will be able to connect to the queue.
$ j2eeadmin –removeJmsDestination <queue name>
Listings 7.21 and 7.22 are available on the accompanying CD-ROM.
import javax.jms.*; import javax.naming.*; import java.io.*; import java.util.*; // This is an example JMS message sender program that sends simple // message objects using the Point to Point mode communication. // The program first sends a name and then a calendar object with // current time. The recipient should receive them in the same order. public class JmsMsgSender { QueueConnection queueConn; QueueSession queueSession; QueueSender queueSender; QueueConnectionFactory queueConnFactory; Queue queue; Context jndiContext; TextMessage txtMessage; ObjectMessage objMessage; String msgQueueName; String myName; JmsMsgSender(String queueName, String helloName) { try { // set the message queue name obtained from command prompt arguments msgQueueName = new String(queueName); myName = new String(helloName); // create initial JNDI context jndiContext = new InitialContext(); // obtain the queue connection factory for the queueu from the initial JNDI context queueConnFactory = (QueueConnectionFactory) jndiContext.lookup("QueueConnectionFactory"); // obtain the queue from the initial JNDI context queue = (Queue) jndiContext.lookup(msgQueueName); // from the queue connection factory create a queue connection object and then // create a queue session object. queueConn = queueConnFactory.createQueueConnection(); queueSession = queueConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); // the queue sender object is used to send messages to the queue queueSender = queueSession.createSender(queue); // create a text message and object message for the session txtMessage = queueSession.createTextMessage(); objMessage = queueSession.createObjectMessage(); // send myName as the first message txtMessage.setText(myName); queueSender.send(txtMessage); System.out.println("Sent '" + myName + "' as first message"); // send a calendar object with current timestamp as the second message Calendar currTime = Calendar.getInstance(); objMessage.setObject(currTime); queueSender.send(objMessage); System.out.println("Sent current time '"+ currTime.getTime().toString()+ "' as second message"); } catch (NamingException ne) { System.out.println("Error while creating/accessing JNDI Initial Context " + "Context : " + ne.toString()); System.exit(-1); } catch (JMSException je) { System.out.println("JMSException occurred while sending a message " + je.toString()); } catch (Exception ex) { System.out.println("Error occured while running the message sender program"); ex.printStackTrace(); System.exit(-1); } } public static void main(String[] args) { try { // make sure that the queue name is provided in the command line argument if (args.length != 2) { throw new Exception("Queue name (one word) and your name (one word) should be specified at command prompt"); } } catch (Exception ex) { System.out.println("Error occured while running the message sender program"); ex.printStackTrace(); System.exit(-1); } JmsMsgSender msgSender = new JmsMsgSender(args[0], args[1]); } }
import javax.jms.*; import javax.naming.*; import java.io.*; import java.util.*; // This is an example JMS message recipient program that receives simple // message objects from the queue using the Point to Point mode communication. // The messages received by the recipient are sent by the JmsMsgSender. java program. public class JmsMsgRecipient { QueueConnection queueConn; QueueSession queueSession; QueueReceiver queueRecipient; QueueConnectionFactory queueConnFactory; Queue queue; Context jndiContext; TextMessage txtMessage; ObjectMessage objMessage; String msgQueueName; JmsMsgRecipient(String queueName) { try { // set the message queue name obtained from command prompt arguments msgQueueName = new String(queueName); // create initial JNDI context jndiContext = new InitialContext(); // obtain the queue connection factory for the queueu from the initial JNDI context queueConnFactory = (QueueConnectionFactory) jndiContext.lookup("QueueConnectionFactory"); // obtain the queue from the initial JNDI context queue = (Queue) jndiContext.lookup(msgQueueName); // from the queue connection factory create a queue connection object and then // create a queue session object. queueConn = queueConnFactory.createQueueConnection(); queueSession = queueConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); // the queue sender object is used to send messages to the queue queueRecipient = queueSession.createReceiver(queue); queueConn.start(); // create a text message and object message for the session txtMessage = queueSession.createTextMessage(); objMessage = queueSession.createObjectMessage(); Calendar currTime = null; while (true) { Message msg = queueRecipient.receiveNoWait(); if (msg != null) { if (msg instanceof TextMessage) { // retrieve the text message and display txtMessage = (TextMessage) msg; System.out.println("First message received : " + txtMessage.getText()); } else if (msg instanceof ObjectMessage) { objMessage = (ObjectMessage) msg; currTime = (Calendar)objMessage.getObject(); System.out.println("Second message received : " + currTime.getTime().toString()); } else { break; } } } } catch (NamingException ne) { System.out.println("Error while creating/accessing JNDI Initial Context " + "Context : " + ne.toString()); System.exit(-1); } catch (JMSException je) { System.out.println("JMSException occurred while receiving a message " + je.toString()); } catch (Exception ex) { System.out.println("Error occured while running the message recipient program"); ex.printStackTrace(); System.exit(-1); } } public static void main(String[] args) { try { // make sure that the queue name is provided in the command line argument if (args.length != 1) { throw new Exception("Queue name (one word) should be specified at command prompt"); } } catch (Exception ex) { System.out.println("Error occured while running the message recipient program"); ex.printStackTrace(); System.exit(-1); } JmsMsgRecipient msgRecipient = new JmsMsgRecipient(args[0]); } }
Figure 7.19 and Figure 7.20 display the console output of the sender and recipient programs, respectively. Although the examples are simple, they are very clear and provide an easy way of understanding the JMS principles. While the example discussed the point-to-point mode, the publisher-subscriber mode is left to the readers, as it is beyond the scope of the discussion. The JMS concepts discussed in this chapter are useful in understanding some of the advanced topics discussed in Chapter 8.
< Day Day Up > |