Using Messenger Services


The asynchronous programming techniques showed thus far work when both the caller and receiver are executing in the same Java virtual machine instance. When the caller and receiver are executing on different machines or at different times, a different strategy is needed. In the Java world, this is handled using Java Messaging Service (JMS). With JMS, applications are written using queues, not method calls. When writing applications use queues, it's very different than using the threading packages because it involves creating messages and then reacting to messages. The messenger package is a toolkit that makes it simpler to write JMS-type applications. It is a faade that makes it more efficient to write JMS applications without hiding access to the JMS classes.

Tip

Using the messenger package requires that you install the Java 2 Enterprise Edition SDK (J2EE SDK), or it requires you to install the JMS package and the appropriate JMS server. The messenger package provides only client-side access to a JMS server. If you want the messenger package to function properly, the JMS client must be working and you must properly configure it. This can be a challenge and does require some knowledge about configuring a JMS server and JMS client. An administrator can probably help you with this configuration. Also, the J2EE SDK does have a JMS server for testing and debugging purposes.

Technical Details for the messenger Package

Tables 6.3 and 6.4 contain the abbreviated details necessary to use the messenger package.

Table 6.3: Repository details for the messenger package.

Item

Details

CVS repository

jakarta-commons-sandbox

Directory within repository

messenger

Main packages used

org.apache.commons.messenger, org.apache.commons.messagelet.*, and javax.jms.* (the messenger package requires some familiarity with the JMS specification)

Table 6.4: Package and class details (legend: [messenger] = org.apache.commons.messenger, [messagelet] = org.apache.commons.messagelet, [jms] = javax.jms).

Class/Interface

Details

[messenger].MessengerManager

A static class instance that holds references to all currently loaded messenger objects. This class is at the center of the entire messenger package.

[messenger].Messenger

The main class used to interact with the JMS server. This class is a faade over the JMS client library.

[messagelet].SubscriptionManager

A class used to manage the Subscriptions, references to the Connections, and the Message Listeners.

[messagelet].model.Subscription

An individual subscription from the subscription.xml class.

[messagelet].model.SubscriptionList

A list of subscriptions.

[jms].*Message

A number of classes that represent an individual message. Read about and inspect these classes before you use them.

Configuring a messenger Client

When you use JMS, using the various connection settings can be tedious . The simplest way to manage these settings is to create some generic routines and abstract the settings to either a class or a configuration file. In the case of the messenger package, the JMS settings are abstracted to a file called messenger.xml . Within the messenger.xml file are the settings used to connect to the JMS implementation that you may be using.

The messenger.xml file can be located anywhere on the classpath, or you can specify the system property org.apache.commons.messenger . Once the file has been located, it is loaded into a singleton that is referenced whenever the messenger package is referenced. Listing 6.16 is a sample messenger.xml file.

Listing 6.16
start example
 <manager> <messenger name="topic"> <jndi lookupName="TopicConnectionFactory"  topic="true"> <property>  <name> com.sun.jms.internal.java.naming.factory.initial  </name>  <value> com.sun.enterprise.naming.SerialInitContextFactory  </value> </property> </jndi> </messenger> <messenger name="queue"> <jndi lookupName="QueueConnectionFactory"  topic="false"> <property> <name>  com.sun.jms.internal.java.naming.factory.initial </name> <value>  com.sun.enterprise.naming.SerialInitContextFactory </value> </property> </jndi> </messenger> </manager> 
end example
 

Listing 6.16 contains two separate blocks of functionality. The first block is the XML tag messenger with the attribute value of topic . The second block is the XML tag messenger with the attribute value of queue . You can see that the messenger topic and messenger queue relate to the JMS topic and JMS queue, respectively. However, that does not need to be the case. It is a convention that has been defined. The XML attribute values topic and queue in Listing 6.16 could have been named myfabtopic and myfabqueue . The child XML elements jndi are important to notice here.

The XML element jndi in Listing 6.16 is the actual configuration used by the JMS layer to connect to the JMS server. The XML elements messenger and jndi are very different in purpose and scope. The XML element messenger determines connection settings and how messages are handled. The XML element jndi defines the JMS factories used to connect to the JMS server. It is important to realize that different JMS servers have different JMS client implementations . Within the directory [jakarta-commons-sandbox]/messenger/src/conf are a number of messenger.xml file examples that provide default connections to different JMS servers.

The following properties can be set on the XML element messenger (properties available from the class org.apache.commons.messenger.MessengerSupport ):

  • name (String): This is the name of the messenger used to identify the messenger in the client code.

  • jndiDestination (Boolean): This specifies whether or not a Java Naming and Directory Interface (JNDI) destination should be used. Do not confuse this with a JMS destination. In implementation terms, the jndiDestination property either connects to an existing destination or creates a new destination if one does not already exist.

  • durable (Boolean): This specifies whether or not the subscriber maintains a durable subscription. A durable subscription is when a subscriber will receive all messages even though an object instance used to receive the messages does not exist. Durable subscriptions cost a lot of overhead.

  • durableName (String): This is an identifier to a message box. When a durable subscription is requested , an identifier is required so that the JMS server knows to which message box to send messages that do not have an active client connection.

  • cacheRequestor (Boolean): This specifies whether or not the TopicRequestor is cached in the context of a thread.

  • noLocal (Boolean): This specifies whether or not messages can be published on the same JMS connection.

  • cacheProducers (Boolean): This specifies whether or not JMS producers should be cached. Note that the messenger package defaults to true .

  • deliveryMode (Integer): This specifies the delivery mode of the individual JMS messages, which can be DeliveryMode.NON_PERSISTENT or DeliveryMode.PERSISTENT . A persistent delivery mode , which is the default, means that a message will travel from the client to the JMS server and then to the individual subscribers or receivers. A non- persistent delivery mode means that the message may or may not travel to the JMS server and then to the individual subscribers or receivers. The message might get lost and remain lost forever.

  • persistentDelivery (Boolean): This is a property to indicate whether or not a delivery is persistent. Unlike the property deliveryMode , which can be multiple different values, the property persistentDelivery is a Boolean value. This means that the deliveryMode can either be persistent or non-persistent. There are only two different delivery modes, so the Boolean flag is adequate to represent all delivery modes. You should generally use this property instead of deliveryMode .

The following properties can be set on the XML element jndi (properties available from the class org.apache.commons.messenger.SessionFactory ):

  • transacted (Boolean): This specifies whether or not the JMS session will support transactions.

  • acknowledgeMode (Integer): This specifies the mode of message acknowledgment when a JMS session does not support transactions. Supported modes are:

    • Session.DUPS_OK_ACKNOWLEDGE : This is a lazy acknowledgement, which means that an acknowledgement may or may not be sent to the sender.

    • Session.AUTO_ACKNOWLEDGE : This is an automatic acknowledgement provided by the session when the client receives the message.

    • Session.CLIENT_ACKNOWLEDGE : This is a flag to indicate whether or not a client should acknowledge a message. When a client receives the message, an acknowledgment is manually created by explicitly calling the message acknowledgment method. The problem with this strategy is that a client might be busy processing a message and might not be able to send the acknowledgment until the processing is complete. This means that although the client has received the message, an acknowledgment of the message is delayed and thus might cause resource problems.

  • acknowledge (String): This is like the property acknowledgeMode , except that the acknowledgment mode is specified by the strings dups_ok , auto , or client . In general, you should use this property.

  • username (String): This specifies the username used to connect to the JMS server.

  • password (String): This specifies the password used to connect to the JMS server.

  • topic (Boolean): This specifies whether or not the destination is a topic or a queue. A value of true indicates a topic, whereas a value of false indicates a queue. A topic is a Publish and Subscribe mechanism, whereas a queue is a message-based mechanism. For more details on the difference between topics and queues, please refer to the JMS documentation.

  • clientID (String): This specifies the client ID used on the specific connection.

  • lookupName (String): This specifies the JNDI lookup name.

Within the XML element jndi are a number of properties identified by a key value pair of XML elements, name and value . The individual properties are specific to the JMS server and most likely are defined in the JMS server documentation. However, the directory [Jakarta-commons-sandbox]/messenger/src/conf contains examples of defining messenger.xml files for various JMS servers.

Configuring Multiple Messenger Clients

When the client loads the messenger.xml file, the configuration is loaded into a singleton. A singleton is used so that the configuration is not repeatedly parsed. This is good to do for performance reasons, but bad for multiple configuration reasons. In the default scenario, it means that only one connection description can be used to connect. In theory, this would mean you could connect to only one JMS server and not another one. However, you can get around this problem in two ways: using a more complex configuration and manually managing the singleton.

The simplest solution programmatically is to use a more complicated messenger.xml file. Look back to Listing 6.16. Here, there is a defined topic and queue; however, like in Listing 6.17, we could define additional topics and queues (note that the XML child elements of the XML element jndi have been removed for clarity).

Listing 6.17
start example
 <manager> <messenger name="topic-j2ee" jndiDestinations="true" persistentDelivery="true"> <jndi lookupName="TopicConnectionFactory"  topic="true" acknowledge="auto"> </jndi> </messenger> <messenger name="queue-j2ee" jndiDestinations="false"> <jndi lookupName="QueueConnectionFactory" topic="false"> </jndi> </messenger> <messenger name="topic-jboss" jndiDestinations="true"> <jndi lookupName="ConnectionFactory" topic="true"> </jndi> </messenger> <messenger name="queue-jboss" jndiDestinations="true"> <jndi lookupName="ConnectionFactory" topic="false" transacted="true"> </jndi> </messenger> </manager> 
end example
 

In Listing 6.17, there are four messenger XML elements. Each individual element represents a specific configuration. For explanation purposes, the XML attribute name has either an appended j2ee or jboss value. The appended j2ee value means that the configuration defined is used to connect to the default JMS server provided by the J2EE SDK. The appended jboss value means that the configuration defined is used to connection to the JBOSS JMS server.

It's acceptable to use the configuration approach to defining different connections. However, the problem with this strategy is that the programming environment has to be aware of the various configurations. The client application needs to know that two connections are used for the same logic, but that different JMS servers are being used. A better approach is to consider the messenger.xml configuration as a number of task-based configurations. A better name than queue-j2ee would be stock-publisher . To manage the stock-publisher messages, the client would write one set of method calls using the messenger package. The details of whether a topic or queue is used and whether or not a transaction is used are configuration issues. The messenger package is very useful because it lets the developer concentrate on writing JMS applications. The details of the session, connection, and other parameters are then configuration issues. Offloading the configuration issues to the administrators makes it simpler to tweak and tune the application appropriate for a specific situation.

As already noted, the other solution to managing multiple configurations is to load the configuration manually. Using manual techniques, the programmer can determine the exact configuration during the execution of the program, as shown in Listing 6.18.

Listing 6.18
start example
 MessengerManager firstSingleton, secondSingleton; MessengerManager.configure( "Messenger.xml"); firstSingleton = MessengerManager.getInstance(); MessengerManager.configure( "JBossMessenger.xml"); secondSingleton = MessengerManager.getInstance(); MessengerManager.setInstance( firstSingleton); 
end example
 

In Listing 6.18, the singleton is stored as an instance to the class MessengerManager . Calling the class method MessengerManager.configure creates the singleton instance. However, calling this method also replaces the currently existing singleton instance. To retrieve the current singleton instance, the class method MessengerManager.getInstance is called. To replace the current singleton instance, without loading a new configuration, the class method MessengerManager.setInstance is called.

Managing configurations manually does have a potential side effect. Consider the case of an application that sends messages and very often changes the singleton instance. In a single threaded application, this is not a problem. However, in a multi-threading scenario, it can be a huge problem. For example, thread A assigns singleton instance AA. A moment later, thread B assigns singleton instance BB. If a moment later thread A decides to send or receive messages, the wrong singleton instance will be referenced, since singleton instance BB is currently the singleton. In this scenario, there are no concurrency issues, since the messenger package uses synchronization. What is problematic is that correct singleton instance may not be set. And there is no way of solving this because that is how the messenger package is defined. The best solution is to define one configuration file and, if necessary, have it contain all of the needed references to different JMS servers.

A Simple Sender

Once you have created the configuration and added it to the classpath, a message can be sent. Listing 6.19 is a simple message-sending application. The application is based on the configuration shown in Listing 6.16.

Listing 6.19
start example
 Messenger messenger = MessengerManager.get( "queue"); Destination destination = messenger.getDestination( "test.queue"); TextMessage message = messenger.createTextMessage( "this is some text"); messenger.send( destination, message); 
end example
 

In Listing 6.19, the class MessengerManager is used to retrieve an active instance of the class Messenger . Unlike Listing 6.18, where the configuration was defined manually, Listing 6.19 loads the configuration implicitly when the class method MessengerManager.get is called. If the variable messenger is null , then the configuration cannot be loaded. When a valid instance of the variable messenger is loaded, you can connect to remote queues or topics and create a destination. The method getDestination connects to a remote queue or topic and is transparent to the end programmer. In the case of Listing 6.19, the identifier test.queue is a queue because the configuration in Listing 6.16 specifies that the identifier queue is a queue. However, the queue could just as easily have been a topic. As already mentioned, which one it becomes depends on the configuration file messenger.xml . Once a destination has been created, a message has to be created. In Listing 6.19, the message is created using the method createTextMessage . To send the message, the method send is called, and the two parameters specify the destination and message.

It is important to note that the class and interface definitions Destination and TextMessage are directly from the JMS library. This means that the messenger package provides only a thin organizational layer on top of the JMS library. This is good because you don't have to worry about any extra baggage, nor do you have to learn anything else about the messenger package other than configuration file.

A Simple Receiver

To receive the message created in Listing 6.19, we create a receiver, as shown in Listing 6.20.

Listing 6.20
start example
 Messenger messenger = MessengerManager.get( "queue"); Destination destination = messenger.getDestination( "test.queue"); Message message; while((message = messenger.receive( destination)) != null) { TextMessage messageText = (TextMessage)message; System.out.println( "Message is " +  messageText.getText()); } 
end example
 

Like in Listing 6.19, the class MessengerManager in Listing 6.20 is used to retrieve an instance to an active class instance of Messenger . In addition, to create a destination, the same method getDestination is used. What is important is that both the sender and receiver use the same configuration file. For this book's examples, the sender and receiver were on the same machine. However, in reality, this would usually not be the case. Therefore, it is important that the administrator does not have too many different versions of the file messenger.xml in use.

To receive a message, the method receive is called. This method accepts only one parameter that represents the destination from where messages are received. In the case of Listing 6.20, the method receive will block waiting until there is a message. Having executed Listing 6.19, we can retrieve a message. A type cast is made to convert the message into the class type TextMessage . This is a legal cast because the original message was text based. Then, to output the message that was retrieved, we call the method getText .

Listings 6.19 and 6.20 show how simple it is to send and receive messages using JMS. It is important to stress that this is why the messenger package is so effective. However, it is also important to stress that most of the magic occurs within the configuration file messenger.xml . Therefore, the administrator must take great care to make sure everything is set up correctly for proper performance and efficiency.

Creating Messages

In Listings 6.19 and 6.20, the messages sent to and from the JMS server were text messages because the method createTextMessage was used. Other types of messages are interface-based and extend the root interface Message . The following sections describe these various types of messages.

The javax.jms.Message

You create the base interface Message by using the method createMessage . Creating a base message is not that useful if messages with content are being sent. The interface Message has no method to manipulate a message body. Primarily, this interface is geared towards manipulating the message headers.

A generic JMS message has three kinds of sections: standard headers, custom-defined headers, and the body. Standard headers are headers that every JMS message has and needs. Examples include the destination, delivery mode, expiration, and priority. Typically, these header properties are modified using explicit method calls like getJMSMessageID and setJMSDestination .

Custom-defined headers are properties manipulated by the JMS provider or application. These types of headers are manipulated using a number of methods that have a naming convention similar to getBooleanProperty and setBooleanProperty . The difference in the various method names is that the type, which in our example is Boolean, would be replaced with Short , Long , or other data types. Each property is stored as a key value pair. It is important to realize that objects other than the default built-in object types like String cannot be stored as a key value pair.

The body is an abstract part of the class that is not implemented, and needs a specific interface or class type (as defined by the sections following this section).

The javax.jms.BytesMessage

The interface BytesMessage extends the interface Message and is used to store messages represented by a number of bytes. You create an instance by calling the method createBytesMessage . An example could be sending a binary picture. The entire message is based on an array of bytes. The arrays are manipulated using methods similar to readBoolean or writeChar . You read or write the array by reading and writing sections of data. This approach is very similar to reading and writing a file in C. The problem with this approach is that there are no boundaries to define the individual data types. For example, if the methods writeInteger and writeInteger were called, then the data could be read using the method call readDouble . The returned answer would be a double value, but not necessarily the correct double value. The byte stream in a Java context is not that useful because it requires the developer to ensure that the data is read like it was written. The only situation where this stream is useful is when you use C and C++ clients.

The javax.jms.MapMessage

The interface MapMessage extends the interface Message and is used to store key value pairs of data elements. You create an instance by calling the method createMapMessage . A message based on the interface MapMessage is like storing pieces of header information in the body. Custom application header information is stored in key value pairs in the same manner as the interface MapMessage . Like the interface BytesMessage , the interface MapMessage can read and write data based on the data type (like the methods setString and getObject) . When you retrieve or set the value, you need to use a key. The key has to be unique and there cannot be multiple keys with the same name. Otherwise, the values will replace each other.

The javax.jms.ObjectMessage

The interface ObjectMessage extends the interface Message and is used to store an object or retrieve one. You create an instance using the method createObjectMessage . If you compare the interface MapMessage to the interface ObjectMessage , it would seem that the message ObjectMessage is superfluous. The interface MapMessage contains a method setObject , which allows a developer to store a key value pair that represents an object. However, this method is misleading because the only object types that can be stored are the Java Object Primitives or byte arrays. Therefore, you need to use the interface ObjectMessage in order to be able to store objects. There are only two methods on the interface ObjectsMessage : setObject and getObject . The object that will be saved as a message has to support the interface Serializable .

The javax.jms.StreamMessage

The interface StreamMessage extends the interface Message and is used to store an array of bytes. You create an instance using the method createStreamMessage . The interface StreamMessage and BytesMessage have identical purposes: to store a stream of byte-based data that represents some type of message. The large difference is that the interface StreamMessage has a type-safe data stream. In the implementation of the interface StreamMessage, whenever a double , long, or String is written, a type identifier is written. This means that if the message sender calls the method writeInteger and writeInteger , a reader will generate an exception if the method readDouble is called. You should generally use the interface StreamMessage instead of BytesMessage .

The javax.jms.TextMessage

The interface TextMessage extends the interface Message and is used to read and write a buffer of text. You create an instance using the method createTextMessage . In the context of this book, this is the preferred way of sending and receiving messages because it allows XML to be stored in the buffer. However, for those that do not want to serialize to XML, the interface ObjectMessage is also acceptable.

Sending and Receiving Commands

Sending and receiving messages is not something that most programmers, or at least pure object-oriented programmers, enjoy. This is to a large degree why the concept of Remote Procedure Call (RPC) was invented. It is much easier to expose an object that is used by a client than to send a message and receive an answer. This goes back to the initial section of this chapter, where we showed you how to write asynchronous code. When you use messaging, the programming style is not much different from that of asynchronous programs, but it's very different from RPC's. The big difference between the asynchronous threading package and JMS is that the JMS infrastructure is more complex. With JMS, you can combine both the messaging and the asynchronous frameworks into a grand event-driven mechanism. However, that would be beside the point.

When you're writing JMS applications that are not Publish and Subscribe mechanisms, they are workload-balancing applications. This means that for every single message added, there is only one read of the message. Therefore, if there are 10 queue readers, only one reader will retrieve the message and process it. Writing message-based applications using this technique makes it possible to almost linearly scale the processing workload.

Message-based workload-balancing applications are written using the design pattern Command . The original intent of the Command design pattern was to facilitate the ability to process generic macros in an application. However, the Command design pattern is much more useful as a workload-balancing best practice.

The essence of the Command best practice is the definition of a generic interface that exposes a single method to perform an operation. The generic interface is defined in Listing 6.21.

Listing 6.21
start example
 public interface Command { public void execute(); } 
end example
 

In Listing 6.21, the method execute is used to perform some execution. The idea of the Command best practice is to rely on the ability of an object to know what operations to perform.

In X-Windows or the Windows API, an event identifier is given to the receiver of the message. The event identifier is then used in a switch statement, and the correct action is executed. You can't maintain event identifiers because every new event requires you to update the receiver. A better approach is to use the Commons Bridge in conjunction with a generic interface that has an execution method to let the object itself decide what to do. This is the purpose of the Command best practice. An implementation of the interface Command is shown in Listing 6.22.

Listing 6.22
start example
 public class MessageBean implements Serializable, Command { private String _message; public MessageBean( String inpMessage) { _message = inpMessage; } public MessageBean() { } public void execute() { System.out.println( "Message is " + _message); } public String getMessage() { return _message; } public void setMessage( String value) { _message = value; } } 
end example
 

In Listing 6.22, the class MessageBean implements the interfaces Serializable and Command . The class MessageBean contains one private data member, _message , which contains a buffer that is sent by the sender and then will be processed by the receiver. Think back a moment to Chapter 5; this bean conforms to all requirements required by the betwixt serialization package.

In Listing 6.22, the message is retrieved and the text string is displayed. The abstract question is how does a receiver know what operation to perform when it receives a message? Maybe one message needs to have its contents displayed and another message requires storage of the information in the database. Listing 6.22 does not address that issue. If you rewrote Listings 6.19 and 6.20, which are responsible for sending and receiving messages, to use the class MessageBean, you would get Listing 6.23.

Listing 6.23
start example
 public void send() { try { Messenger messenger = MessengerManager.get("queue"); Destination destination = messenger.getDestination( "test.queue" ); MessageBean bean = new MessageBean( "message to send"); TextMessage message = messenger.createTextMessage( convertBeanToXML(bean)); messenger.send( destination, message); } catch( Exception ex) { System.out.println( ex.getMessage()); ex.printStackTrace(); } } public void receive() { try { Messenger messenger =  MessengerManager.get("queue"); Destination destination = messenger.getDestination( "test.queue" ); Message message; while((message = messenger.receiveNoWait( destination)) != null) { TextMessage messageText = (TextMessage)message; Command command = convertXMLToBean( messageText.getText()); command.execute(); } } catch( Exception ex) { System.out.println( ex.getMessage()); ex.printStackTrace(); } } 
end example
 

Listing 6.23 looks remarkably similar to Listings 6.19 and 6.20. There are a few small changes, however. In the method send, the class MessageBean is instantiated and then passed to a method convertBeanToXML . The method convertBeanToXML contains code that uses the betwixt package to serialize the MessageBean to an XML buffer. The XML buffer is then sent as a text message.

In the method receive, something more complex happens. The method convertXMLToBean converts the XML buffer to a class instance. The implementation of the method convertXMLToBean is also based on the betwixt package. What is generic is that the method convertXMLToBean returns an interface instance of Command . The receiver therefore does not need to know any specifics regarding the implementation. The receiver now just needs to call the method execute to process the message.

The advantage of this approach is that the sender or receiver does not need to know any specifics about the actual bean. This approach has been used successfully in messaging applications where processing tasks are distributed on various computers. Sometimes, though, the interface Command as defined in Listing 6.21 has to be experimented with because other generic data might need to be passed with every message. The interface as defined by Listing 6.21 is very useful when there is only a single sender and a single receiver operation. Sometimes it is necessary to carry through workflow-type operations where there are multiple processing steps. In those cases, the interface needs to be an abstract class similar Listing 6.24.

Listing 6.24
start example
 abstract class CommandBase implements Command, Serializable { private int _executionStep; public void execute() { if( initialize() == true) { executeLocal( _executionStep); destroy(); _executionStep ++; } } abstract public boolean initialize(); abstract public void execute( int step); abstract public void destroy(); } 
end example
 

In Listing 6.24, the class CommandBase still implements the interface Command . The class CommandBase implements the method execute , which calls the lifecycle methods initialize , execute, and destroy . The idea of the abstract class CommandBase is to be able to serialize and execute the business logic multiple times. The methods initialize and destroy are used to establish and cleanup resources. The method execute with a parameter is like the Command.execute method, except that the parameter identifies how many times the implementation has been executed.

One other variation is to pass in a context class to the method execute. In Listing 6.22, the class MessageBean output some data. The problem with the method execute in this format is that the class does not know anything about the execution environment. In more complicated examples where database connections and storage locations are needed, this poses a problem. The implementation could figure things out using discovery mechanisms, as described in Chapter 3.

A better solution would be to pass in a Context object and let the implementation use it for specific environment information. An example of a context object and the modified Command interface is Listing 6.25.

Listing 6.25
start example
 interface Context { public String getRootDirectory(); } interface ContextCommand { public void execute( Context ctxt); } 
end example
 

Listening for Messages

Instead of waiting for messages, it is also possible to react to messages using a message listener. A message listener is a class that implements the interface MessageListener . Using the Messenger class, you can manually set up a listener. Within the messenger package is another package, messagelet.model , that enables the developer to define a subscription list using a configuration file. If you combine the subscription file with the Command best practice, you create the basics of a workload-balancing system. Especially useful is that a configuration file defines the workload that is processed. Listing 6.26 shows an example.

Listing 6.26
start example
 <?xml version="1.0" encoding="UTF-8"?> <subscriptions> <subscription connection="queue" subject="test.queue"> <listener className="com.devspace.jseng.asynchronous.SomeMessage"/> </subscription> </subscriptions> 
end example
 

The subscription configuration file is defined by a number of XML subscription elements. Listing 6.26 shows the essentials of what is necessary to define a subscription. The XML element subscription has two attributes: connection and subject . The attribute connection references a connection identifier from the messenger.xml file used when the subscription configuration file is processed. The attribute subject is the name of the topic or queue that the message will be read from. Within the XML element subscription can be the XML elements listener , servlet, or bridge . In Listing 6.26, the XML element listener is used to indicate that when a message arrives, you should call the object defined by the attribute className. The XML element servlet allows the client to read from a servlet, which is also part of the messenger framework (and is beyond the scope of this book). The XML element bridge is used to redirect a message from one queue to another, as shown in Listing 6.27.

Listing 6.27
start example
 <subscription connection="queue" subject="foo.input"> <bridge outputConnection="queue" outputSubject="foo.output"/> </subscription> 
end example
 

In Listing 6.27, the XML element bridge has two attributes: outputConnection and outputSubject . The attribute outputConnection defines the messenger.xml connection, which is used to connect to the JMS server. The attribute outputSubject is the name of the queue or topic where the incoming message will be sent.

Once you have defined the subscription configuration, you need to create the listener defined in Listing 6.26. A sample listener that incorporates the Command best practice is shown in Listing 6.28.

Listing 6.28
start example
 public class MyMessageHandler implements MessageListener { private Command convertXMLToBean( String buffer) throws Exception { BeanReader reader = new BeanReader(); reader.registerBeanClass( MessageBean.class); return (Command)reader.parse(new StringReader(buffer)); } public void onMessage(Message message) { try { TextMessage messageText = (TextMessage)message; Command command = convertXMLToBean( messageText.getText()); command.execute(); } catch( Exception ex) { System.out.println( ex.getMessage()); ex.printStackTrace(); } } } 
end example
 

In Listing 6.28, the class MyMessageHandler implements the interface MessageListener , which only has one method. The method onMessage is the callback method that is called when a message arrives. In the implementation of the onMessage method, the variable message is cast to the type TextMessage . The contained text message is then converted into a Command interface instance using the method convertXMLToBean . To be able to process the message, the class method command.execute is called. This implementation is very similar to Listing 6.23, except that the class is called automatically by the subscription mechanism in the Messenger package.

All of the configuration files are wired together so that everything happens automatically. Listing 6.29 shows how to wire everything together.

Listing 6.29
start example
 MessengerManager manager = MessengerManager.getInstance(); SubscriptionManager subscriber = new SubscriptionManager(); subscriber.setMessengerManager( manager); SubscriptionDigester digester = new SubscriptionDigester(); SubscriptionList subscriptionList = (SubscriptionList)digester.parse( "/src/common/subscriptions.xml"); subscriber.setSubscriptionList(subscriptionList); subscriber.setServletContext( null); subscriber.subscribe(); for( Iterator iter = manager.getMessengerNames(); iter.hasNext(); ) { String name = (String) iter.next(); Messenger messenger = manager.getMessenger( name ); messenger.getConnection().start(); } 
end example
 

Listing 6.29 contains two main sections. The first section is the one that uses the words "subscription" and "subscriber" in their identifier. The second section consists of the for loop. The first section uses the class SubscriptionManager , which is a class responsible for putting all of the pieces together and creating a subscription mechanism. The file messenger.xml is associated with the subscription using the method setMessengerManager . The class SubscriptionDigester is responsible for parsing and loading the subscription configuration file. To make the subscription list do something useful, you must associate the list with the class SubscriptionManager using the method setSubscriptionList . The method setServletContext is not used in this case since the application is running on its own. However, if Listing 6.29 were running in the context of a servlet, then we would have to use the method setServletContext . You assemble all the pieces by calling the method subscribe. In the implementation of the method subscribe, the subscription list is the iterator and the listeners are created and associated as per the subscription configuration file.

In the second section, we start the various connections defined in the messenger.xml file by iterating them. The iterator is retrieved from the method getMessengerNames . The iterator contains a list of String- based names, used to load an individual Messenger configuration. Once the messenger has been retrieved, you need to create the connection needs using the method getConnection().start() .

The code to manage the subscriptions is that simple, but there is a hidden catch. It is the threading. Listing 6.29 should run in its own thread. Or the subscriptions should run in their own thread. This is called using a JMS message consumer thread, which is the class ConsumerThread . You can associate the class ConsumerThread with the class Subscription by using the class method Subscription.setConsumerThread . However, in any case, the exact nature of how to manage the threading is an application-implementation detail.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net