HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Power JMS, by Tarak Modi

What is a facade? In software engineering it's a design pattern. One possible definition of a facade is: "A higher-level interface that provides a unified way of accessing a subsystem and as a result makes the subsystem easier to use."
Thus, in constrast to most design patterns that help break the system up into subsystems, the facade design pattern rolls up a complex subsystem into one, easy-to-use system. A facade can provide a simple default view of the subsystem that's good enough for most clients. Only clients needing more customizability will need to look beyond the facade.

Why Does JMS Need It?
JMS serves as an excellent foundation for enterprise applications. Although the JMS API is very concise, using JMS effectively can be challenging and there are many potential booby traps that novice users may fall prey to. Furthermore, it may not be very appealing to force every developer in your organization to learn JMS. Most organizations would avoid the above "issues" by creating a reusable library (i.e., a facade) that encapsulates all the JMS-related code/knowledge. Unfortunately, even this library would require a learning curve, albeit a smaller one (hopefully).

In this article I present an alternative approach based on the Java protocol handler architecture. The major advantage of this approach is that most Java developers are already familiar with using this architecture via the URL class in the java.net package. For example:

URL url = new URL("http://www.javasoft.com/index.html");

An additional, and by no means minor, benefit is that this is a time-tested architecture built into the Java language/platform itself. Best of all, this architecture is flexible enough to allow the "plug-in" of new protocol (handlers). That's exactly what I demonstrate in this article by creating a protocol handler for JMS.

This JMS protocol handler will make programming with JMS as simple as the example shown in Listing 1.

Notice that Listing 1 doesn't contain any JMS-specific code. In fact, the only giveaway is that the name of the protocol is "jms" in the URL; otherwise the code is exactly the same as that for http or ftp from a Java program. The JMS protocol handler takes care of all the JMS grunt work for us. After all, that's what a good facade is supposed to do, right?

An Overview of the Protocol Handler Architecture
One of the benefits of using Java is the excellent support it provides for networking. Not only is this support extensive, but it's easy to leverage, in most cases requiring the developer to master only a few classes, such as the java.net.URL class. This support is built on top of an elaborate (and extensible) architecture called the Java Protocol Handler architecture. In this section I discuss this architecture and how to extend it with your own protocol handler. Then in the next section, we actually implement a JMS protocol handler using this knowledge.

The gateway to this architecture is (you guessed it) the java.net.URL class, which encapsulates a URL string. The general form of a URL is:

protocol://host:port/filepath#ref

Examples include "http://www.javasoft.com/index.html" or "file: ///C:/temp/junk.txt". (The three slashes "///" is not an error; you'll see why in a moment.) In the first example, the protocol is http, the host name is www.javasoft.com, and the filepath is index.html. Since no port number has been specified, the protocol handler will use a protocol-specific/default port, which in this case will be port number 80. In the second example, the protocol is file and the filepath is C:/temp/junk.txt. Note that in this case neither the host name nor the port number has been specified (which would have been between the second and third slashes), so the file protocol handler will use protocol-specific/default values for these.

As an aside, the format of the URL string for the jms protocol will be:

jms://Queue/<QueueName>

or

jms://Topic/<TopicName>

Note that the jms protocol does not have a concept of a host name, port number, or filepath. Instead the host name is actually the messaging style and the filepath is the destination.

The URL class does not know how to access the resource stream represented by the URL string. Instead, it relies on a set of other classes to handle this. When a new URL class instance is created it resolves the URL string to a protocol-specific handler (e.g., the protocol handler class). This protocol handler knows how to create a connection to the resource represented by the URL string and return an object corresponding to this connection. Since this resolution occurs at construction time, any attempt to construct an instance of URL with an unknown/invalid protocol will throw a MalformedURLException during the construction itself. The relevant portion of the URL constructor is shown below:

if(handler == null &&
(handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException(
"unknown protocol: " + protocol);
}

I'll discuss the getURLStreamHandler method in detail later in this article.

Sun provides protocol handlers for several standard and widely used protocols such as http, ftp, mailto, and gopher. Protocol handlers must follow a strict naming convention. The class must always be named Handler. The package name must always have the protocol name as its last part. For example, Sun's protocol handler for the http protocol is called Handler and is in the package sun.net.www.protocol.http. Note that the package name ends with "http". In our case the "jms" protocol handler will be in the jmsbook.jms package and will be called Handler. To make the Java runtime aware of your own protocol handlers, you must use the java.protocol.handler.pkgs system property. This property is set equal to a "|" delimited list of package name prefixes. These prefixes will be used to resolve the specified protocol name to a protocol handler object. Note that these package names must not include the last part (e.g., the protocol name). So in our case this property will be set to jmsbook (and not jmsbook.jms), as follows:

java.protocol.handler.pkgs=jmsbook

The getURLStreamHandler Method
Why does Java impose such a strict naming convention for protocol handlers? The answer is found by examining the URL class source code, more specifically the getURLStreamHandler method implementation, which is called to resolve a protocol name to the corresponding protocol handler. The relevant portion of this method is shown in Listing 2. Read the inline comments for the explanation of the code fragment.

Only one protocol handler object is created per VM per protocol. A new protocol handler is created the first time it's required and is then cached for later use. This means that multiple threads may use the same protocol handler simultaneously. Thus the protocol handler implementation must be thread- safe. The URL class instance caches the protocol handler in a static hash table, which allows any URL instance to access this handler. To get a better feel for this, let's take a look at the remainder of the getURLStreamHandler method. Once again, read the comments for the explanation (see Listing 3).

The openConnection and openStream Methods
At this point the URL has successfully resolved the protocol string to a protocol handler. The openConnection method may be used to gain access to a connection object. A connection object must implement the URLConnection interface and is used to send and receive data to and from the resource stream, respectively. The URL instance merely delegates the openConnection method call to the protocol handler as shown below:

public URLConnection openConnection()
throws java.io.IOException {
return handler.openConnection(this);
}
The URL class also provides a helper method, openStream, for clients interested only in receiving data from the resource stream. This method is shown below:
public final InputStream openStream()
throws java.io.IOException {
return openConnection().getInputStream();
}

Our JMS Protocol Handler
As discussed above, the URL class serves as the gateway into Java's protocol handler architecture. The URL class itself has very limited functionality beyond resolving a protocol string into a protocol handler. There are two key pieces that a protocol handler implementer must provide/implement: a Handler class and a URL Connection class. Having said that, let's take a look at the implementation for our JMS protocol handler. A class diagram showing all the pieces of the JMS protocol handler architecture and how they fit together is shown in Figure 1.

figure 1
Figure  1:  The JMS protocol handler architecture

Note: This article is based on a chapter in Tarak Modi's upcomming book The Essence of JMS: A Developer's Guide (to be published by Manning Publications). According to the author, the actual chapter includes a much more detailed discussion of the concepts presented in this article along with the complete source code for the JMS protocol handler and associated test programs. Modi has offered to provide the complete source code and test programs to all JDJ readers who request them from him at tmodi@att.net.

The Handler Class
As with all protocol handlers, the JMS protocol handler conforms to the following rules:

  1. The class name is Handler.
  2. It extends the URLStreamHandler class and provides a concrete implementation of the openConnection abstract method.
  3. Its package name has the protocol name as its last part (i.e., it's in a package whose last part is "jms". In our case the handler is in the jmsbook.jms package.

Since the JMS protocol handler extends the URLStreamHandler class, it must provide an implementation of the openConnection method. The openConnection method is responsible for returning a connection object (an instance of JmsURLConnection, which we'll see next) to the caller. The caller may then use this connection object to access the resource stream. The handler decides how to initialize the JmsURLConnection object based on the host name portion of the URL as shown below in pseudo-code:

// pseudo-code
if(u.getHost().equals("Queue"))
return a new JmsURLConnection to use with JMS Queues.
else if(u.getHost().equals("Topic"))
return a new JmsURLConnection to use with JMS Topics.
else
throw new IOException("Host name must be Topic or Queue.");

Configuring the Handler
The handler has two member variables that must be initialized: a reference to a queue connection and a reference to a topic connection. These references are obtained via a queue and a topic connection factory, respectively. The JMS specification does not define a standard way of getting the initial queue and topic connection factories. As a result, each vendor that provides a JMS-compliant messaging product must define its own way of allowing clients to get these initial connection factories. One of my primary design goals is not to get tied to a specific JMS provider. To achieve this I must isolate any vendor-specific code so that it does not affect the core architecture and can easily be replaced and tested.

I have defined two interfaces, JmsQueueConnectionFactory and JmsTopicConnectionFactory, which are shown below:

public interface JmsQueueConnectionFactory {
public javax.jms.QueueConnectionFactory
getQueueConnectionFactory(java.util.Properties props)
throws javax.jms.JMSException;
}
public interface JmsTopicConnectionFactory {
public javax.jms.QueueConnectionFactory
getQueueConnectionFactory(java.util.Properties props)
throws javax.jms.JMSException;
}
These interfaces are defined in the jmsbook.jms package. Both have one method each. For example, the JmsQueueConnectionFactory interface has a method called getQueueConnectionFactory that returns the initial queue connection factory. This method gets an instance of a java.util.Properties object as its parameter, which contains all the vendor-specific messaging product information that the class implementing this interface will require to get the queue connection factory. One such class that implements the JmsQueueConnectionFactory interface is created for every vendor's messaging product that's to be supported. An example of such a class for Sun Microsystems' Java Message Queue product is shown below:
package jmsbook.jms
public class SunJmsQueueConnectionFactoryImpl
implements JmsQueueConnectionFactory {
public javax.jms.QueueConnectionFactory
getQueueConnectionFactory(java.util.Properties
props) throws javax.jms.JMSException {
javax.jms.QueueConnectionFactory factory =
new com.sun.messaging.QueueConnectionFactory();
return(factory);
}
}

Similarly, an example of a topic connection factory class for Sun Microsystems' Java Message Queue product is shown below:

package jmsbook.jms
public class SunJmsTopicConnectionFactoryImpl
implements JmsTopicConnectionFactory {
public javax.jms.TopicConnectionFactory
getTopicConnectionFactory(java.util.Properties
props)
throws javax.jms.JMSException {
javax.jms.TopicConnectionFactory factory =
new com.sun.messaging.TopicConnectionFactory();
return(factory);
}
}
The JMS protocol handler class is configurable through a properties file, the name of which is specified in the jmsbook.jms.propertiesFile system property. An example is shown below:
java -Djmsbook.jms.propertiesFile=C:/temp/jmsProtocol.
properties
. . . the rest of the java command
The properties file must have two properties, JmsQueueConnectionFactory and JmsTopicConnection- Factory, that specify which queue and topic connection factory classes to use. The properties file may contain any other properties in addition to these, such as JMS provider-specific properties that may be used by the connection factory class implementations. The handler loads the properties from the properties file, creates a new instance of the specified factory class, and calls the appropriate get method on the factory, passing the entire properties collection to the method as its parameter. As a result, this method is able to gain access to any JMS provider-specific properties defined in the properties file.

A sample properties file for configuring the JMS protocol handler to use Sun Microsystems' Java Message Queue product is shown below:

# The factory to use to get the initial Connection Factory
JmsQueueConnectionFactory=
jmsbook.jms.SunJmsQueueConnectionFactoryImpl
JmsTopicConnectionFactory=
jmsbook.jms.SunJmsTopicConnectionFactoryImpl

# Optional
# Sun Microsystems' Java Message Queue product specific
properties
.
.
.
Once the handler has references to the vendor's connection factories, it can get the queue and topic connections from these as specified by the JMS.

The JmsURLConnection Class
As you've seen before, the openConnection method in the JMS protocol handler class returns an instance of the JmsURLConnection class. This class extends the URLConnection class and overrides the set/getRequestProperty, getInputStream, getOutputStream, and connect methods. I'll discuss each one of these methods next.

The set/getRequestProperty Methods
Messages in JMS are associated with a delivery mode, a priority, and a time-to-live. The JmsURLConnection class provides default values for three "request" properties and allows the client (e.g., user) to configure these properties at any point in time. The default values of these properties are shown below:

// The delivery mode; default is persistent.
private int persistence = DeliveryMode.PERSISTENT;
// The priority; default is 9 (highest)
private int priority = 9;
// The time-to-live; default is 0 (forever)
private int ttl = 0;

A client can get the value of any of these properties at any time by calling the getRequestProperty method and passing in the name of the property required. An example of querying the connection for the delivery mode is shown below:

// uc is a JmsURLConnection
String persistent = uc.getRequestProperty("persistent");

In the above code fragment, if the value of persistent is "true" after the statement is executed, then the delivery mode is persistent. Note the name of the persistence property is persistent. Similarly, to find out the priority and time-to-live properties, call the getRequestProperty method with the property names "priority" and "timeToLive", respectively.

To change the value of any of these properties use the setRequestProperty method. For example, to change the delivery mode to nonpersistent, change the priority to 4, and change the time-to-live to 10 seconds:

setRequestProperty("persistent","false");
setRequestProperty("priority","4");
setRequestProperty("timeToLive","10000");
If an invalid property name or value is passed in to either of these methods, a RuntimeException will be thrown.

The Connect Method
All this method does is set the connected member variable in the base class to true.

public void connect() throws IOException {
this.connected = true;
}

Remember, all the connection-related work has already been performed in the handler class, so we don't need to do anything here.

The getInputStream Method
This method checks to see if the JMS connection it has is a queue or a topic connection. Based on this it creates the appropriate session and message consumer. It then creates a new instance of the JmsInputStream class, passing in the session and the message consumer. JmsInputStream is a private class that extends the java.io.InputStream class and provides a concrete implementation of the read method. When the client calls the read method's (or a method that results in the read method's being called, such as readUTF, readDouble, or any other <read> method on any decorating input stream), it first checks if there are any more bytes in the buffer. If no more bytes exist, it calls the private method readMessage. This method checks if there are any more bytes remaining in a previous (partially read) message. If no such message exists, it calls the receive method on the message consumer that was passed in during construction.

The getOutputStream Method
This method checks to see if the JMS connection it has is a queue or a topic connection and then, based on this, creates the appropriate session and message producer. It then creates a new instance of the JmsOutputStream class, passing in the session and the message producer. JmsOutputStream is a private class that extends the java.io.OutputStream class and provides a concrete implementation of the write method.

A client then calls the write method (or a method that results in the write method's being called, such as writeUTF, writeDouble, or any other <write> method on any decorating output stream). A client can call write as many times as needed.

Finally, when the entire message is written, the client must call flush to actually send the message. The flush method will call the private writeMessage method that contains all the logic for dealing with the message producer.

For example, let's assume that a message consists of some basic information about a person, such as name, age, and sex. Such a message could be sent as shown in Listing 4.

A client could receive this message as shown in Listing 5.

Summary
Probably one of the most significant advancements in software engineering in the past few years has been the widespread acceptance of component-based programming and design by contract. I've absolutely bought into these concepts and always emphasize designing your enterprise applications without relying on or getting married to a specific JMS provider by remaining loyal to the JMS specification. In this article I've taken the design-by-contract concept one step further by creating a facade that allows you to decouple your application not only from specific JMS providers, but from JMS itself by using Java's protocol handler architecture. Additional (and by no means minor) benefits of this technique include a reduced learning curve for most Java developers, since they're already familiar with using URLs and the ability to leverage a well-designed and time-tested architecture of the Java platform.

Reference

  • Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1995). Design Patterns - Elements of Reusable Object-Oriented Software. Addison-Wesley.
Author Bio
Tarak Modi, a certified Java programmer, is a lead systems architect at Online Insight where he's responsible for setting, directing, and implementing the vision and strategy of the company's product line from a technical and architectural perspective. Tarak has worked with Java, C++, and technologies such as EJB, Corba, and DCOM, and holds a BS in EE, an MS in computer engineering, and an MBA with a concentration in IS. Mr.Modi can be contacted at: tmodi@att.net

	


Listing 1

// create a new URL with our custom "jms" protocol.
URL url = new URL("jms://Queue/ModiQueue");
        URLConnection uc = url.openConnection();

// Send a message
        DataOutputStream dos =
                        new DataOutputStream(uc.getOutputStream());
        dos.writeUTF("Hello!");
        dos.flush();

// Receive the message
DataInputStream dis = new DataInputStream(uc.getInputStream());
String message = dis.readUTF();

// close the streams.
dos.close();
dis.close();



Listing 2

// Get the list of package prefixes
// protocolPathProp has been defined as
// "java.protocol.handler.pkgs"
String packagePrefixList = null;
        PackagePrefixList = (String)
                java.security.AccessController.doPrivileged(
                        new sun.security.action.GetPropertyAction(
                                protocolPathProp,""));

        // Add the standard protocols package to the list!
        // First, if any package prefixes were found, append
        // another delimiter, "|", to the end.
        if (packagePrefixList != "") {
            packagePrefixList += "|";
        }

        // and now append "sun.net.www.protocol" to the end.
        // Important:
        // Since this package is appended at the end of user
        // specified packages, a user can override any of the
        // Sun provided protocol handler implementations, such
        // as the one for http.
        packagePrefixList += "sun.net.www.protocol";

        // And now parse through the list...
        // Remember, "|" is the delimiter.
        StringTokenizer packagePrefixIter =
            new StringTokenizer(packagePrefixList, "|");

        // Keep going until either we get a handler or
        // no more tokens remain.
        // Note that there will always be at least
        // one token, sun.net.www.protocol.
        while (handler == null && packagePrefixIter.hasMoreTokens()) {

                // Get the next token
                String packagePrefix =
                        packagePrefixIter.nextToken().trim();
                try {
                        // Create the fully qualified class name.
                        // Eg. jmsbook + jms + ".Handler"
                        String clsName = packagePrefix + "." +
                                                protocol + ".Handler";

                Class cls = null;
                        try {
        // Now try loading the class with that name.
                        clsClass.forName(clsName);
        }
catch (ClassNotFoundException e) {
                        ClassLoader cl =
                                        ClassLoader.getSystemClassLoader();
          if (cl != null) {
                                        cls = cl.loadClass(clsName);
                                }
                        }
                        if (cls != null) {
        // create a new instance.
                                handler  = (URLStreamHandler)cls.newInstance();
                        }
}
catch (Exception e) {
                        // any number of exceptions can get thrown here
                        // move onto the next token...
                }

} // while loop.


Listing 3

// This is the static hash table used
// to cache the protocol handlers.
// All access to this table must be synchronized.
static Hashtable handlers = new Hashtable();

static synchronized URLStreamHandler getURLStreamHandler(
                String protocol) {

// Have we already resolved this protocol?
URLStreamHandler handler =
                (URLStreamHandler)handlers.get(protocol);
// Maybe not...
if (handler == null) {
// Use the factory (if any)
// We will not consider this case.
// In a nutshell, a factory implements the
// URLStreamHandlerFactory interface and is
// registered with the URL instance either during
// construction or using the
// setURLStreamHandlerFactory method.
// A factory can only be set once and similar to the
// protocol handlers is shared by all URL instances.
if (factory != null) {
                handler =
                        factory.createURLStreamHandler(protocol);
}

// still don't have a handler...
if (handler == null) {
// All the logic to
resolve a protocol
// string to a protocol handler.
// Plug in the implementation that
// we saw above here
                        }

// Cache the handler if one was found.
                        if (handler != null) {
        handlers.put(protocol, handler);
                        }
                }

// Return the handler to the caller.
return handler;
    }


Listing 4

// uc is a JmsURLConnection
// Wrap/Decorate the JmsOutputStream with a DataOutputStream
        DataOutpputStream dos =
                new DataOutputStream(uc.getOutputStream());
// Write the name (string), sex (string)
// and age (long) to the stream.
        dos.writeUTF(name);
        dos.writeUTF(sex);
        dos.writeLong(age);
// send the message.
        dos.flush();
// done
        dos.close();
		
Listing 5


        // uc is a JmsURLConnection
// Wrap/Decorate the JmsInputStream with a DataInputStream
        DataInpputStream dis =
                new DataInputStream(uc.getInputStream());
// Read the name (string), sex (string)
// and age (long) from the stream.
        String name = dos.readUTF(name);
        String sex = dos.readUTF(sex);
        Long age = dos.readLong(age);
        // done
        dis.close();



  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.