Mar 1, 1999 12:00 AM PT

Messaging makes its move, Part 2

Put the finishing touches on your own Java-based messaging service by adding the required JMS veneer

As we learned last month, the Java Message Service Specification (JMS spec) defines an interface for message services but does not define an implementation. Unlike the JDK, if you download the JMS source code from Sun's Web site (see Resources) and examine it, you'll find only interfaces; there are no classes at all (well, actually there are two -- but they are helper classes and not really part of the specification). After a moment's reflection it should be clear why this would be true.

There exists a large body of legacy enterprise applications built around existing messaging products. Given this fact, the JMS architects decided it was more important to allow enterprise Java developers to develop applications that worked within the context of an existing messaging infrastructure than to try to get them to adopt an entirely new product. Of course, developers should be able to create applications without creating additional dependencies on any particular vendor -- thus the JMS spec would be vendor neutral but compatible with existing products.

I've decided to create an implementation of the JMS API in Java for two reasons:

  1. For those inclined to technical challenges, creating a Java-based implementation provides an excellent introduction to the practice of writing message-oriented middleware.

  2. For those interested in learning to use the JMS API, creating a Java-based implementation provides a reference to play with. Without it, you'd have to purchase an existing messaging product and the Java interface as well.

I'll begin by presenting the JMS API as it would appear to someone creating an implementation -- that is, as it would appear from the inside.

But I need to share with you one caveat before I begin.

While the JMS API isn't the most complicated of the Java Enterprise APIs, it isn't the least complicated, either. Due to time and space constraints, I haven't made a heroic effort to implement the entire JMS API Specification. I have, however, taken the time to implement a workable subset. Just to be on the safe side, I'll let you know where my implementation comes up short.

You might find it useful to begin with a refresher. Last month I built the classes that implemented the basic underlying functionality of a message service and I presented the basic philosophy behind the JMS API. If you already feel comfortable with that material, then please continue.

A tale of two domains

Recall that the elements of the JMS API are divided into two domains, each representing one of the two leading models of messaging provided by existing messaging products. The two models are the point-to-point model and the publish/subscribe model.

While the behavior of the classes within each domain differ, their APIs are nearly identical. It's the APIs and their use that we're interested in this month.

If you glance at the relationship between the point-to-point and publish/subscribe interfaces, you'll see what I mean.

Point-to-pointPublish/subscribeParent interface
QueueTopicDestination
QueueConnectionFactoryTopicConnectionFactoryConnectionFactory
QueueConnectionTopicConnectionConnection
QueueSessionTopicSessionSession
QueueSenderTopicPublisherMessageProducer
QueueReceiverTopicSubscriberMessageConsumer

The first column lists the principle interfaces that define the point-to-point half of the API. The second column lists the principle interfaces that define the publish/subscribe half of the API. The third column lists the parent interfaces from which both the point-to-point and the publish/subscribe interfaces inherit. Note how similar the two domains are from the standpoint of these interfaces.

Since the two APIs are nearly identical, you can get a good feel for the use of both interfaces by looking at either.

Destinations

The Destination, Queue, and Topic interfaces represent administered objects. Administered objects are created by an administrator and are registered with a directory. They represent globally accessible resources. In this case, they are used to encapsulate the identity (or address) of a message destination such as a queue or a topic. They are not themselves a destination. They provide a platform-independent way to encapsulate provider-specific addresses. Destination objects support concurrent use.

The following code demonstrates how the Queue interface and associated implementation class Queue_Impl are implemented. The code for the Topic interface is nearly identical.

First the Queue interface:

public
interface Queue
extends Remote
{
  public
  String
  getQueueName()
  throws RemoteException;
}

Then the Queue_Impl interface:

public class Queue_Impl extends UnicastRemoteObject implements Queue { private String _stringQueueName = null;

public Queue_Impl(String stringQueueName) throws RemoteException { _stringQueueName = stringQueueName; }

public String getQueueName() throws RemoteException { return _stringQueueName; }

public int hashCode() { return _stringQueueName.hashCode(); }

public boolean equals(Object object) { return object.equals(_stringQueueName); } }

Connection factories

The ConnectionFactory, QueueConnectionFactory, and TopicConnectionFactory interfaces represent administered objects. They encapsulate a set of configuration parameters that have been defined by an administrator. A client uses a ConnectionFactory to create a Connection with a JMS provider. They simplify the administration of a message service in a large-scale enterprise setting. ConnectionFactory objects support concurrent use.

Since our implementation has no interesting administrative infrastructure, the only method implemented is the method that returns a connection. The following interface and implementation classes illustrate how this looks within the queue domain. Once again, the topic source code is nearly identical.

First the QueueConnectionFactory interface:

public
interface QueueConnectionFactory
extends Remote
{
  public
  QueueConnection
  createQueueConnection()
  throws RemoteException;
}

Then the QueueConnectionFactory_Impl interface:

public class QueueConnectionFactory_Impl extends UnicastRemoteObject implements QueueConnectionFactory { public QueueConnectionFactory_Impl() throws RemoteException { }

public QueueConnection createQueueConnection() throws RemoteException { return new QueueConnection(); } }

Connections

The Connection, QueueConnection, and TopicConnection interfaces represent an active connection to a JMS provider. They are the conduit through which communication flows. A client uses them to create a Session with the JMS provider. They provide a single point for all communication activities -- thus enabling resource (such as connection) pooling as well as authentication and security. Connection objects support concurrent use.

The following code illustrates the implementation within the queue domain.

public class QueueConnection implements Serializable { private Hashtable _hashtable = new Hashtable();

private HQueue lookup(Queue queue) throws MalformedURLException, NotBoundException, UnknownHostException, RemoteException, IOException { HQueue hqueue = null;

if ((hqueue = (HQueue)_hashtable.get(queue)) != null) { return hqueue; }

hqueue = (HQueue)Naming.lookup(queue.getQueueName());

_hashtable.put(queue, hqueue);

return hqueue; }

void send(Message message, Queue queue) throws NotBoundException, RemoteException, IOException { lookup(queue).send(message); }

Message receive(Queue queue) throws NotBoundException, RemoteException, IOException { return lookup(queue).receive(); }

public QueueSession createQueueSession() { return new QueueSession(this); } }

Sessions

The Session, QueueSession, and TopicSession interfaces represent a single threaded context for sending and receiving messages. A client uses them to create one or more MessageProducers or MessageConsumers. They also provide a factory for creating messages and define a serial order for the messages they consume or produce. Sessions provide a natural way for clients to organize interactions with a provider.

The following code illustrates an implementation.

public class QueueSession implements Serializable { private QueueConnection _queueconnection = null;

QueueSession(QueueConnection queueconnection) { _queueconnection = queueconnection; }

void send(Message message, Queue queue) throws NotBoundException, RemoteException, IOException { _queueconnection.send(message, queue); }

Message receive(Queue queue) throws NotBoundException, RemoteException, IOException { return _queueconnection.receive(queue); }

public QueueSender createSender(Queue queue) { return new QueueSender(this, queue); }

public QueueReceiver createReceiver(Queue queue) { return new QueueReceiver(this, queue); } }

Message producers

The MessageProducer, QueueSender, and TopicPublisher interfaces represent objects that are used to send messages to a destination.

public class QueueSender implements Serializable { private QueueSession _queuesession = null; private Queue _queue = null;

QueueSender(QueueSession queuesession, Queue queue) { _queuesession = queuesession; _queue = queue; }

public void send(Message message) throws NotBoundException, RemoteException, IOException { _queuesession.send(message, _queue); } }

Message consumers

The MessageConsumer, QueueReceiver, and TopicSubscriber interfaces represent objects that are used to receive messages sent to a destination.

public class QueueReceiver implements Serializable { private QueueSession _queuesession = null; private Queue _queue = null;

QueueReceiver(QueueSession queuesession, Queue queue) { _queuesession = queuesession; _queue = queue; }

public Message receive() throws NotBoundException, RemoteException, IOException { return _queuesession.receive(_queue); } }

I've provided the complete source code for those readers who can't sleep until they see it all. For instructions on downloading, extracting, setting up, and running the code, please read the accompanying sidebar.

Conclusion

With the close of this month's column, I begin a short vacation. After over two years of writing, it's time to step back and reorient myself. In the two years since I started writing this column, Java has grown from a fledgling to an adult. It now seems poised to bury itself within the heart of the enterprise. When I return, I plan to continue to show you how to use the Java platform to solve your problems. Until then.

Todd Sundsted has been trying to make computers do something useful for many years now. Though originally interested in building distributed object applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is president of Etcee, which offers training, mentoring, consulting, and software development services.

Learn more about this topic

  • For the source code in .zip format, see http://www.javaworld.com/jw-03-1999/howto/jw-03-howto.zip
  • For the source code in .tar format, see http://www.javaworld.com/jw-03-1999/howto/jw-03-howto.tar.gz
  • Sun's JMS home page http://www.javasoft.com/products/jms/
  • The JMS spec http://www.javasoft.com/products/jms/docs.html
  • The JMS interface source code http://www.javasoft.com/products/jms/jms-101a-src.zip
  • Read Todd's previous How-To Java columns http://www.javaworld.com/topicalindex/jw-ti-howto.html