In this section we will describe messaging architecture, illustrate its logical similarity with P2P architecture, and describe the Java Messaging Service (JMS). We will also discuss JMS providers (which are part of the J2EE platform), and put them to service. What Is Messaging?Messaging is a mechanism through which two or more entities (applications or software modules) can communicate with each other. Communicating entities operate independently of each other; that is, they are not bound to each other in any way. For example, if one entity sends a message to another entity, it will not wait for the response from the other side. This type of messaging is called asynchronous communication. Messaging Versus Request-ResponseConsider what happens when you use an Internet browser program such as Internet Explorer. The browser sends an HTTP request and receives an HTTP response from a Web server. The browser remains busy after issuing the HTTP request until it receives the response (normally an HTML file), although the browser does not have much to do other than waiting for the response. In this way the browser is bound to the Web server, at least for a while. This is called synchronous communication, and it's the main difference between request-response and messaging models. With messaging, we have asynchronous communication that does not require waiting for response. Therefore, messaging entities remain independent of each other. Telephonic conversation is a real-life example of synchronous communication between humans. On the other hand, the exchange of letters or emails is asynchronous in nature. The only difference between email and messaging is that messaging always takes place between software components and software applications. Unlike email, messaging never involves human interaction. Messaging Clients and Messaging ProvidersIn the request-response model, we normally have clients and servers. Clients request and servers respond (serve). Servers are normally designed to serve a number of clients simultaneously, and therefore have more powerful computing capabilities compared to clients. On the other hand, the messaging model has only clients and no servers. This gives rise to the equality of clients. Each client is supposed to a peer of every other client. However, this does not mean that there is no requirement to handle administration issues, such as the following:
Management tasks such as these call for a management entity, called a messaging provider. Messaging Clients as Peers in P2PMessaging clients are logically similar to the concept of peers in P2P. A client acting as a peer can send messages for several purposes for example, the discovery of other peers, searching for information, and so on. Imagine a messaging application in which clients follow the XML formats of JXTA. This messaging application will become very similar to a JXTA-based P2P application. This similarity emphasizes the role of messaging in P2P applications. Role of Messaging Providers in P2PJXTA calls for an architecture in which messages sent by peers can be forwarded to other peers and so on, until they reach the required destination peer. An example of this type of messaging is the JXTA search specification. If a JXTA peer wants to search for a specific bit of information, it will send an XML message to the relevant peers. The peers receiving this message might in turn forward this search request to other relevant peers. This way, the search message has the chance of eventually reaching a peer that has the requested information. This type of architecture can be realized through messaging. You will need to build a messaging provider application that can also act as messaging client to other messaging providers. In this way, it can forward specific messages from its clients to other providers, who will in turn forward the message to their clients and other providers. Message-Oriented Middleware (MOM)Based on the concepts of messaging discussed previously, proprietary implementations are available from different vendors. Some examples of proprietary messaging frameworks are the following:
These messaging frameworks are called message-oriented middleware (MOM). Sun released the specification of JMS, which eventually became a part of the J2EE specification. The reference implementation of JMS is now included in J2EE SDK v1.3. Java Messaging ServiceJMS aims to provide access to vendor-specific MOM implementations through a standard Java API. JMS allows you to have proprietary implementations of JMS administrative objects. This means that vendors are free to implement administrative objects in any manner they want. (JMS manages these objects administratively rather than programmatically. Therefore, they are called administered objects. Administered objects are meant to administrate topics and connections as described previously in the section "Messaging Clients and Messaging Providers.") The only restriction is at the API level. JMS providers talk to JMS clients according to the API; JMS clients will access JMS providers to get messaging services. The interaction between JMS clients, the JMS provider, and administered objects is shown in Figure 12.1. Figure 12.1. The JMS architecture.Figure 12.1 shows four components: JMS clients, the JMS provider, administered objects, and the JNDI (Java Naming and Directory Interface) namespace. These components work as follows:
JMS allows two messaging domains: publishing/subscribing and point-to-point. The publishing/subscribing domain is a topic-based messaging mechanism in which message producers publish their messages on a topic. Message consumers subscribe to the topics in order to consume (receive) messages. Every message will be delivered to all subscribers of the topic. The point-to-point domain is based on queues. Message senders will send their messages to message queues. Each message queue is destined for one consumer. Consumers will receive messages from their queues and acknowledge receipt to senders. In this way, each message will be received by only one message consumer. Both queues and topics are referred to as destinations. A JMS-administered object is supposed to administrate destinations and connections to destinations. To provide connections to destinations, JMS provides two interfaces: QueueConnectionFactory and TopicConnectionFactory. A JSM client will use either of the two connection factories to connect to a JMS provider. Putting JMS to WorkWe will now discuss the architecture of a complete messaging system based on JMS. While designing a JMS messaging system, you will need to address the following issues:
Let's see how you will accomplish these tasks by taking a simple messaging application as a design example. A JMS-Based Messaging ApplicationWe will now demonstrate the design and implementation of a simple messaging application that is very similar to the concept of P2P. The architecture of our application is shown in Figure 12.2. Figure 12.2. Four instances of a JMS-based messaging application.We have shown four instances of our application running simultaneously. Each instance will act as a peer. Theoretically, there can be any number of simultaneous instances of this application, but for the sake of simplicity and clarity we'll discuss only four instances. Think of each instance as a peer in our messaging system. Another simplification that we have assumed is that the messaging peers have already discovered each other that is, all four peers know the addresses of the other peers. Therefore, they can send and receive messages to each other. Each messaging peer has three modules:
Using the GUI, each peer can send a message to any other peer. Each peer can also register itself with any other peer to receive messages related to any particular topic. Let's see how our messaging application works. We'll need to perform the following steps detailed in the following sections. Configuring the ProviderIn this application we have used Sun's J2EE SDK v1.3 that includes a JMS provider implementation, so you will need a running J2EE server. Once the server is running, use the following command to create a topic connection factory (an administrator object): j2eeadmin -addJmsFactory MyTopicConnectionFactory topic Even if you don't do this, the application will run using the default topic connection factory. Then use the following commands to create topics (also administered objects): j2eeadmin -addJmsDestination Topic1 topic j2eeadmin -addJmsDestination Topic2 topic This will create two topics on the JMS provider, which is now set. You will need to configure the provider for every instance of this application. Logic for the Messaging ClientWe have written two classes for our messaging client: MessageConsumer (Listing 12.1) and MessageProducer (Listing 12.2). MessageConsumer.java implements a JMS client that consumes JMS messages, and MessageProducer.java implements a JMS client that produces JMS messages. Listing 12.1 MessageConsumer.javaimport javax.swing.*; import javax.jms.*; import javax.naming.*; import java.io.*; import java.util.*; public class MessageConsumer { // We would like to keep a reference of TopicConnection // and TopicSession so that we can destroy it at the end. private TopicConnection topicConnection = null; private TopicSession topicSession = null; // This Subscriber will listen for incoming messages. private TopicSubscriber topicSubscriber = null; // This listener will receive the incoming messages. private TextListener topicListener = null; // The TextArea to disply the messages. private JTextArea textArea = null; // Names of machines over the network. // These machines will have JMS Providers running. // In real applications, names of machines should come // from the GUI. private final String COMPUTER1 = "computer6"; private final String COMPUTER2 = "computer13"; private final String COMPUTER3 = "computer14"; private final String COMPUTER4 = "computer15"; // Connect to the JMS Provider and start listening. public void connectToProvider (String topicName, String jmsProvider) { // We will get a reference of System properties. // We will add the Properties related to JMS functionality. Properties env = System.getProperties(); env.put("com.sun.jms.internal.java.naming.factory.initial", "com.sun. enterprise.naming.SerialInitContextFactory"); // Port number where the Naming service of // remote JMS Provider is listening. env.put("rg.omg.CORBA.ORBInitialPort","1050"); try { // Set the network address of target machine // where JMS Provider is running. if (jmsProvider.equals("Server1")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER1); else if (jmsProvider.equals("Server2")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER2); else if (jmsProvider.equals("Server3")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER3); else if (jmsProvider.equals("Server4")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER4); // Get an object of JNDI InitialContext // from given JMS Provider. Context jndiContext = new InitialContext(env); // Get JMS Administrator Objects TopicConnectionFactory topicConnectionFactory = ( TopicConnectionFactory) jndiContext.lookup("TopicConnectionFactory"); Topic topic = (Topic) jndiContext.lookup(topicName); // Establish the connection. topicConnection = topicConnectionFactory.createTopicConnection(); // Create the session. topicSession = topicConnection.createTopicSession (false, Session. AUTO_ACKNOWLEDGE); // Create the subscriber for the topic of interest. // Specify a TextListener object (inner class) // who will receive messages. topicSubscriber = topicSession.createSubscriber(topic); topicListener = new TextListener(); topicSubscriber.setMessageListener(topicListener); textArea.setText("starting the listener for messages"); // We are all set, Start listening. topicConnection.start(); } catch (NamingException e) { textArea.append ( "JNDI lookup failed: "+e.toString()); } catch (JMSException e) { textArea.append("JMS Exception occurred: " + e.toString()); } } // connectToProvider() // Stop Listening for messages and close the Connection. public void stopListening() { if (topicConnection != null) { try { topicSubscriber.close(); topicSession.close(); topicConnection.close(); textArea.setText("\nStoped Listining" ); } catch (JMSException e) { textArea.setText("\nException in listing" ); } } } // stopListening() public void setDisplayArea (JTextArea textArea) { this.textArea = textArea; }// setDisplayArea // Inner class to receive messages. private class TextListener implements MessageListener { public void onMessage(Message message) { TextMessage msg = null; try { // Our application can only handle Text Messages. if (message instanceof TextMessage) { msg = (TextMessage) message; textArea.append( "\n\n The Message is Received: \n\t" + msg. getText()); } else { textArea.append( "\n Message of wrong type: " + message.getClass().getName()); }//else } catch (JMSException e) { textArea.append("JMSException in onMessage(): " + e.toString( )); } catch (Throwable te) { textArea.append("Exception in onMessage():" + te.getMessage()); }//catch } // onMessage() } // TextListener class }// Message Consumer class Listing 12.2 MessageProducer.javaimport javax.jms.*; import javax.naming.*; import java.util.*; public class MessageProducer { // We would like to keep a reference of TopicConnection, Session // and TopicPublisher so that we can destroy it at the end. private TopicConnection topicConnection = null; private TopicPublisher topicPublisher = null; private TopicSession topicSession = null; // Names of machines over the network. // These machines will have JMS Providers running. // In real applications, names of machines should come // from the GUI. private final String COMPUTER1 = "computer6"; private final String COMPUTER2 = "computer13"; private final String COMPUTER3 = "computer14"; private final String COMPUTER4 = "computer15"; // Connect to the JMS Provider and send the message. public String connectToProvider (String topicName, String jmsProvider ,String msg ) { // We will get a reference of System properties. // We will add the Properties related to JMS functionality. Properties env = System.getProperties(); env.put("com.sun.jms.internal.java.naming.factory.initial", "com.sun. enterprise.naming.SerialInitContextFactory"); // Port number where the Naming service of // remote JMS Provider is listening. env.put("rg.omg.CORBA.ORBInitialPort","1050"); try { // Set the network address of target machine // where JMS Provider is running. if (jmsProvider.equals("Server1")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER1); else if (jmsProvider.equals("Server2")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER2); else if (jmsProvider.equals("Server3")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER3); else if (jmsProvider.equals("Server4")) env.put("org.omg.CORBA.ORBInitialHost",COMPUTER4); // Get an object of JNDI InitialContext //from given JMS Provider Context jndiContext = new InitialContext(env); // Get JMS Administrator Objects TopicConnectionFactory topicConnectionFactory = ( TopicConnectionFactory) jndiContext.lookup("TopicConnectionFactory"); Topic topic = (Topic) jndiContext.lookup(topicName); // Establish TopicConnection and TopicSession. topicConnection = topicConnectionFactory.createTopicConnection(); topicSession = topicConnection.createTopicSession(false, Session. AUTO_ACKNOWLEDGE); // Create the publisher for the topic of interest. topicPublisher = topicSession.createPublisher(topic); // Create Message Object and set the text. TextMessage message = topicSession.createTextMessage(); message.setText(msg); // We are all set, Send message. topicPublisher.publish(message); } catch (JMSException e) { String err = new String("Exception occurred: " + e.toString()); return err; } catch (NamingException e) { String err = new String ("Error in JNDI context= " + e.toString()); return err; } finally { if (topicConnection != null) { try { topicPublisher.close(); topicSession.close(); topicConnection.close(); } catch (JMSException e) { return "Connection Closing Exception: "+ e.toString(); } }//if } // finally return "Message Sent Successfully"; } //connectToProvider() } // end MessageProducer Class The MessageConsumer ClassAs its name implies, the MessageConsumer class is responsible for tasks related to message consuming. This includes
The MessageConsumer class has a public method connectToProvider that takes two strings as parameters. The first parameter is the name of the topic for which the MessageConsumer wants to register itself to receive messages. The second parameter is the provider address (the address of a JMS provider inside the other peer). We'll call it a remote provider. The ConnectToServer method implements the following sequence:
Our MessageConsumer has an inner class called TextListener, which is an asynchronous event handler. The TextListener class implements the MessageListener interface, which requires implementing just one method named onMessage. onMessage receives control whenever a new message is detected. In the onMessage method, you define the actions to be taken when a message arrives. Our implementation of the onMessage method simply displays the incoming message in a text area. The MessageProducer ClassWhereas the MessageConsumer class has the responsibility of receiving (or consuming) messages related to particular topics, the MessageProducer class is used to send messages to their corresponding topics (destinations), so that the messages can be consumed by interested MessageConsumer objects. The MessageProducer class has a method named connectToProvider that works very much like the connectToServer method of the MessageConsumer class. The only difference occurs after the connection and session are both established. This time a publisher is created instead of a subscriber, and a message is published with the publisher. The message will reach the remote provider, which will route it to all interested peers. GUI for the Messaging ApplicationWe have implemented a simple JFC-based GUI for this messaging application. It puts the MessageProducer and MessageConsumer classes to work. The GUI is one of the downloads from this chapter at this book's Web site. Limitations of our Messaging SystemThis messaging application presents a skeleton design and implementation of a messaging system. The purpose is to demonstrate JMS. You can build your own messaging logic on top of a similar architecture. |