![](/user_photo/1438_p9ksI.png)
Mastering Enterprise JavaBeans™ and the Java 2 Platform, Enterprise Edition - Roman E
..pdf![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL551x1.jpg)
Understanding Java Remote Method Invocation (RMI) 533
RMI Example: A Message Queue for Distributed Logging
Traditionally, when software components communicate, they do so in a synchronous manner. That is, when component A invokes a method on component B, component B executes immediately. Component A must block (that is, wait) until component B is done. This is shown in Figure A.10.
In many scenarios (especially server-side development), however, it is nice to have components communicate in an asynchronous manner. This means component B does not execute immediately. It also means component A does not block. How is this accomplished?
One way is to get a third party involved. The third party acts as a storage for deferred invocations. Each invocation component A makes is stored as a message
Component A |
|
Component B |
|
|
|
invoke()
<<wait>> |
<<perform work>> |
<<return>>
Figure A.10 Synchronous communication.
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL552x1.jpg)
534 M A S T E R I N G E N T E R P R I S E J A V A B E A N S
in this third party’s internal queue. The queue is used to keep an ordering of the messages. When component B wants to service the invocation, it contacts the third party and reads the message off the queue. Component A is a producer of data for the queue, and component B is a consumer of data. By using this networked queue of data, we can have distributed components service requests at their leisure, in an asynchronous manner.
This technology is called Message Queueing. A Message Queue is the queued storage that stores messages from distributed objects across the enterprise. They are responsible for accepting new messages as they arrive and delivering others as they are requested. Message Queues can also have their messages be transactional in nature, gaining all the benefits of transactions that we listed in Chapter 10. Message Queues are shown in Figure A.11.
Message Queues fall under the general software heading of Message-Oriented Middleware (MOM). There are a number of MOM vendors on the market, including IBM, Microsoft, and BEA Systems. In the Java world, you can accomplish
Component A |
|
Message Queue |
|
Component B |
|
|
|
|
|
<<put message>>
<<return>> |
|
<<get message>> |
<<return>>
<<perform work>>
Since Component A stores messages on the queue, it is now free to perform other operations. Component B can process the request at its leisure.
Figure A.11 Asynchronous communication using Message Queues.
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL553x1.jpg)
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL554x1.jpg)
536 M A S T E R I N G E N T E R P R I S E J A V A B E A N S
on only the channel you’re using, much as if it were a TV channel or radio frequency that you were tuning in. The code for the Channel class is shown in Source A.4.
package com.wiley.compBooks.roman.rmi.mq;
import java.util.Random;
/**
*Abstraction representing a channel for sending messages.
*Running Java code can make as many copies of this as necessary,
*so that multiple distributed objects can communicate on the
*same channel.
*/
public class Channel implements java.io.Serializable {
// A single, static randomizer for channel id generation
private static Random rand = new Random(System.currentTimeMillis());
private String id, name;
/**
*Creates a new Channel.
*@param name The name of the channel. */
public Channel(String name) {
this.name = name;
/*
* Create a new, random identifier for this channel */
this.id = String.valueOf(rand.nextInt());
}
/**
*Because we use Channels as elements of a hashtable, we
*hash based on the channel id. We don't hash based on
*the Channel name because that may not be unique.
*/
public int hashCode() { return id.hashCode();
}
public boolean equals(Object obj) { return id.equals(obj.toString());
}
Source A.4 Channel.java (continues).
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL555x1.jpg)
Understanding Java Remote Method Invocation (RMI) 537
public String toString() { return id;
}
/**
* Returns the channel name. */
public String getName() { return name;
}
}
Source A.4 Channel.java (continued).
The IMessageQueue Interface
The Message Queue itself will be structured as follows. The Message Queue class, MessageQueue, is accessed by RMI clients using its remote interface, IMessageQueue. IMessageQueue exposes methods to (for example) put and get messages on internal queues representing specific channels. All Message Queue operations happen through this interface. The code for IMessageQueue is shown in Source A.5.
Notice that each method throws a RemoteException, as required by RMI.
package com.wiley.compBooks.roman.rmi.mq;
import java.rmi.Remote;
import java.rmi.RemoteException; import java.io.InputStream;
/**
*Remote Interface for the Message Queue. Clients should use
*this API for communicating with the Message Queue.
*/
public interface IMessageQueue extends Remote {
/**
*Initializes a channel for a particular client. Our
*MessageQueue will initialize an internal queue for
*storing messages on that channel.
*
* @param name The name of the channel to create.
*
Source A.5 IMessageQueue.java (continues).
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL556x1.jpg)
538 M A S T E R I N G E N T E R P R I S E J A V A B E A N S
* @return The newly created channel */
public Channel createChannel(String name) throws MessageQueueException, RemoteException;
/**
* Call to close channel */
public void destroyChannel(Channel channel) throws MessageQueueException, RemoteException;
/**
*Waits for the next message to occur in this channel,
*and returns that message.
*/
public Object getMessage(Channel channel, long timeout) throws MessageQueueException, RemoteException;
/**
* Puts a message on the queue of a particular channel. */
public void putMessage(Channel channel, Object message) throws MessageQueueException, RemoteException;
}
Source A.5 IMessageQueue.java (continued).
The MessageQueueException Exception
If you noticed in the preceding code, each method on the IMessageQueue interface also threw a MessageQueueException. This is an application-level exception, and it is meant to denote logical problems, such as invalid channels. We’ll reserve the java.rmi.RemoteExceptions for Java RMI to indicate networking problems. This is a good design philosophy that you should follow when writing your RMI programs (in fact, EJB enforces the separation of application-level and system-level exceptions, which we describe in Chapter 4).
Our exception simply extends the normal java.lang.Exception class. The code for this command is detailed in Source A.6.
The MessageQueue Class
Internally, our MessageQueue class uses a hashtable for storing messages. Each hashtable entry represents one channel. Thus, we use channel identifiers as hash values for the hashtable. The value of each hash entry is a queue—to store messages for that particular channel. Thus, we’re using a hashtable of queues. This
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL557x1.jpg)
Understanding Java Remote Method Invocation (RMI) 539
package com.wiley.compBooks.roman.rmi.mq;
/**
* Exceptions thrown by MessageQueue */
public class MessageQueueException extends Exception {
public MessageQueueException() { super();
}
public MessageQueueException(Exception e) { super(e.toString());
}
public MessageQueueException(String s) { super(s);
}
}
Source A.6 MessageQueueException.java.
enables us to have as many channels as we want, with a message queue for each channel. This is illustrated in Figure A.13.
Hashtable
|
|
|
|
|
|
|
Channel 1: |
|
Message 1A |
|
Message 1B |
|
Message 1C |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Channel 2: |
|
Message 2A |
|
|
|
|
|
|
|
|
|
|
|
Channel 3: |
|
Message 3A |
|
Message 3B |
|
|
|
|
|
|
|
|
|
|
Figure A.13 Our Message Queue architecture.
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL558x1.jpg)
540 M A S T E R I N G E N T E R P R I S E J A V A B E A N S
The actual channel identifiers are abstracted out by the Channel class. You create a new Channel by calling the createChannel method on IMessageQueue. You can pass these Channels around, clone() them, and so on. The Channel object implements java.io.Serializable, so you pass it around by value. By giving each client the same Channel object, you can have many clients all sharing the same channel.
The following procedure shows how the Message Queue can be typically used:
1.Start up the MessageQueue remote object server. MessageQueue registers itself with the local RMI registry.
2.Your client looks up the IMessageQueue object over the network using the RMI lookup facilities.
3.Your client calls IMessageQueue.createChannel() to create a new channel and queue for storing messages. This returns a Channel object.
4.Your client can then give the Channel object to any other party, such as another process that wants to send or receive messages on the same channel.
5.Anyone who owns a Channel object can send messages by calling IMessageQueue.putMessage() with the correct Channel. The messages can be any Java objects. Anyone who wants to receive messages can similarly call IMessageQueue.getMessage().
6.After everyone’s done using the Message Queue, the channel can be destroyed by calling IMessageQueue.destroyChannel().
One interesting thing about our Message Queue is that we don’t dictate what kinds of objects are passed. All we say is that each object has to be a java.lang.Object. This means that both java.io.Serializable messages and java.rmi.Remote messages are possible, for both pass-by-value and pass-by-reference semantics. If the client tries to store non-RMI-safe objects, a java.rmi.RemoteException will automatically be thrown.
The basic idea of a Message Queue brings up a very basic Computer Science problem called the readers/writers problem: What if someone tries to read a message when there are no messages on the queue?
We have two possibilities here. We can either throw an exception if there is no data available, or we can wait for data to become available. The problem with throwing an exception is that client code needs to continuously poll the message queue for any available messages. This is definitely not a scalable solution.
Therefore, we’ll enforce that any clients who request messages are suspended until a message has arrived. To do this, we use Java’s built-in threading support. When a client asks for a message, if the queue is empty we wait() the thread, suspending the client. This means that both the client’s thread and the remote
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL559x1.jpg)
Understanding Java Remote Method Invocation (RMI) 541
Threads and Transactions
Our message queue synchronizes all operations to putMessage() and getMessage() messages on channel queues. This effectively makes each put and get a critical section, and it ensures that every put and get operation is atomic with respect to other threads.
If you’ve read the transactions chapter of this book (Chapter 10), you may recognize how analogous this is to performing atomic transactions. This is no accident—transac- tions and object synchronization are highly related.
calling thread are effectively suspended. When someone puts a new message on the queue, we call notifyAll(), which wakes up any clients that we called wait() on.
Of course, strictly speaking, we should also consider the opposite half of the readers/writers problem: What happens if the queue overflows when there are too many messages on it? We’ll make sure to implement our queue to be theoretically unlimited in length, so we don’t have to worry about that situation. The code for MessageQueue is given in Source A.7.
package com.wiley.compBooks.roman.rmi.mq;
import java.util.*; import java.io.*; import java.rmi.*;
import java.rmi.registry.*; import java.rmi.server.*;
import com.objectspace.jgl.Queue;
/**
*MessageQueue is a generic Buffer for passing information.
*The MessageQueue is a Hashtable, where each hash entry
*represents a "channel" that someone wishes to listen to.
*Each hash entry is a queue of messages for that channel.
*Listening threads are suspended until messages arrive.
*This allows networked applications to take advantage of
*event-driven networked programming but have events occur
*synchronously.
*/
public class MessageQueue extends UnicastRemoteObject implements IMessageQueue {
Source A.7 MessageQueue.java (continues).
Go back to the first page for a quick link to buy this book online!
![](/html/1438/356/html_GGOgEBURPw.5rnp/htmlconvd-hMnKQL560x1.jpg)
542 M A S T E R I N G E N T E R P R I S E J A V A B E A N S
/**
* main() creates the Message Queue. */
public static void main(String args[]) { if (args.length != 1) {
System.err.println(
"Usage: MessageQueue <port to bind RMI Registry to>"); System.exit(-1);
}
try {
MessageQueue queue = new MessageQueue(Integer.parseInt(args[0]));
}
catch (Exception e) { System.err.println(e);
}
}
/*
* Hashtable for storing channel queues of messages */
private Hashtable dataHash = new Hashtable();
/**
*Initialization.
*@param port Port to register MessageQueue at.
*/
public MessageQueue(int port) throws Exception, RemoteException { super();
System.out.println("");
System.out.println("MessageQueue starting up...");
/*
* Set the security manager */
try { System.setSecurityManager(
new java.rmi.RMISecurityManager());
}
catch (java.rmi.RMISecurityException e) {
throw new Exception("Security violation " + e.toString ());
}
/*
* Start up an RMI Registry at designated port
Source A.7 MessageQueue.java (continues).
Go back to the first page for a quick link to buy this book online!