Add the power of asynchronous processing to your JSPs

Create custom JSP tags to use with JMS

1 2 3 Page 2
Page 2 of 3

Next, the doAfterBody() calls the writeMessage() private method, which sends the message off to the specified destination.

That method proceeds as follows:

writeMessage parses the destination URL as shown below:

   String[] parts = parseDestination();

To parse the URL, the writeMessage() method calls the parseDestination() method, which returns a string array of two components: the destination type (queue or topic) and the destination name. If you do not format the destination according to the rules specified earlier in this article, a RuntimeException will result.

Depending on the type of destination, writeMessage will use the appropriate JMS messaging model (point-to-point or publish-and-subscribe).

For example, if the destination type is queue, the following code executes:

   QueueConnection connection = 
   JMSQueueConnectionFactory.getConnection();
   try
   {                     
      QueueSession session = connection.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
      Queue queue = session.createQueue(parts[1]);
      TextMessage msg = session.createTextMessage();
      msg.setText(message);
      QueueSender sender = session.createSender(queue);
      sender.send(msg);
      sender.close();
      session.close();
   }
   catch(Exception e)
   {
      e.printStackTrace();
   }
   finally
   {
      JMSQueueConnectionFactory.releaseConnection();
   }

Note the call to the static methods getConnection() and releaseConnection() on the JMSQueueConnectionFactory class. Those methods obtain and release a queue connection, respectively. Also note that the call to releaseConnection() appears within the finally section to ensure execution no matter what. For now, ignore those method calls. I will provide further details on the JMSQueueConnectionFactory class in a later section.

Now that we have a queue connection, the code basically follows the point-to-point model's standard sequence for sending a message: use the queue connection to create a new queue session; use the session to create a new queue sender; and use the sender to send the message to the queue.

Once the writeMessage() method returns, the doAfterBody() method returns the SKIP_BODY constant to inform the Web server that the body has been processed.

Finally, the handler overrides the release() method. The Web server calls release() before reusing a handler class's instance. release() is responsible for cleaning up the handler instance so that the Web server can reuse the handler as a brand-new instance.

Listing 2 shows the complete implementation of the write tag:

Listing 2: Write tag

Next, let's take a look at the tag handler implementation for the read tag. As this handler is similar to the write tag's handler, I will only discuss the differences.

Since read's handler does not need to manipulate the tag body, it extends the TagSupport class and overrides the doStartTag() method. doStartTag()'s implementation simply calls the private readMessage() method and prints the results to the page as shown below:

    pageContext.getOut().print(readMessage());

readMessage() follows the same steps as the writeMessage() method in the write tag handler: it parses the destination and executes some piece of code based on the destination type. For example, if the destination type is queue, the following code will execute:

   String message = null;
   QueueConnection connection = 
   JMSQueueConnectionFactory.getConnection();
   try
   {                     
      QueueSession session = connection.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
      Queue queue = session.createQueue(parts[1]);
      QueueReceiver receiver = session.createReceiver(queue);
      TextMessage msg = (TextMessage)receiver.receive();
      message = msg.getText();
      receiver.close();
      session.close();
   }
   catch( Exception e )
   {
       e.printStackTrace();
   }
   finally
   {
      JMSQueueConnectionFactory.releaseConnection();
   }
   return(message);

The code above resembles the code executed in the writeMessage() method, with a destination of type queue.

Again, note the calls to the static methods getConnection() and releaseConnection() on the JMSQueueConnectionFactory() class. I will go into the details of those methods in the next section.

After obtaining the queue connection, the code follows the point-to-point messaging model's standard sequence for message retrieval. After retrieving the message, readMessage() extracts the text String from the message and returns it to the caller -- in this case, the doStartTag() method.

After readMessage() returns, the doStartTag() method returns the SKIP_BODY constant to inform the Web server to skip the tag's body.

Listing 3 shows the complete implementation of the read tag handler.

Listing 3: Read tag

Add a healthy dose of efficiency

Now let's look at the mysterious JMSQueueConnectionFactory class, which I've avoided so far. Two factors encouraged me to create that class:

  1. JMS does not specify a standard method for obtaining a queue connection factory from a JMS provider. Actually, JMS recommends that JMS providers allow the use of JNDI to gain access. Many JMS providers do not follow that recommendation yet and, as a result, most clients are forced to use provider-specific code to obtain the connection factory. I did not want that provider-specific code in the tags themselves. JMSQueueConnectionFactory isolates the code and, as I will discuss in the enhancements section, you can easily configure that class to allow portability across different JMS providers.
  2. JMS specifies that obtaining a queue connection to a JMS provider may be expensive, both in terms of time and resources. That probably holds true for a majority of providers. As a result, some sort of caching of connections is prudent. The JMSQueueConnectionFactory class does that for you.

Also, I decided to make JMSQueueConnectionFactory a servlet for the following reasons:

  1. I can instruct a Web server such as Tomcat to automatically load the servlet at startup. That ensures that the queue connection is ready when the JSP page using the custom tags needs it.
  2. The Web server provides me with deterministic destruction by guaranteeing to call the destroy() method during shutdown (unless the Web server crashes).
  3. The JSP content developer does not have to perform any extra initialization or cleanup work.
  4. The custom tags do not have to complete any extra initialization or cleanup work.

The JMSQueueConnectionFactory servlet is straightforward. The Web server calls the init() method at startup, during which the servlet receives the queue connection factory and uses it to obtain and cache a queue connection as shown below:

   public void init(ServletConfig config) throws ServletException
   {
      super.init(config);
      try
      {
         fiorano.jms.rtl.FioranoInitialContext ic = null;
         ic = new fiorano.jms.rtl.FioranoInitialContext();
         ic.bind ();                                      
         QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory)ic.lookup("primaryqcf");
         ic.dispose();
        JMSQueueConnectionFactory.connection = queueConnectionFactory.createQueueConnection();
        JMSQueueConnectionFactory.connection.start();
        JMSQueueConnectionFactory.lockCount = new MyInteger();
        shuttingDown = false;
      }
      .
      .
      .

Note that the code here is specific to Fiorano's FioranoMQ, which is the JMS provider that I use.

Just before the Web server shuts down, it calls the destroy() method, which will close the queue connection to ensure proper cleanup. That servlet will never invoke, so the service() method throws a RuntimeException.

Finally, the servlet has two static methods, getConnection() and releaseConnection(); the custom tags use these methods to obtain and release a queue connection, respectively. The same queue connection is reused and returned to all callers, and a simple reference-counting mechanism prevents the servlet's premature destruction. The same connection can be returned to all callers because a JMS connection object is thread-safe, i.e., it allows multithreaded access. I have a similar servlet for topic connections called the JMSTopicConnectionFactory.

Listings 4 and 5 show the complete implementation of those servlets.

Listing 4: JMSTopicConnectionFactory

Listing 5: JMSQueueConnectionFactory

Step 3: Test it out

To use the custom JSP tags created here, you will need a Web server that supports JSP 1.1. I generally use Tomcat 3.1, as it is a great Web server, and the price is right. I have created three JSP files, which you will need to actually use the custom tags. Test.jsp tests the point-to-point capability, while Test2.jsp and Test3.jsp together test the publish-and-subscribe capability of the JMS provider -- in this case FioranoMQ. If you point your browser to use Test2.jsp, it will hang until you run Test3.jsp in another browser because Test2.jsp is waiting for a message that Test3.jsp publishes. You can run Test2.jsp in multiple browsers, and all of them will receive the message published by Test3.jsp.

The JMSQueueConnectionFactory and JMSTopicConnectionFactory, servlets, and all the test JSP files are available for download in Resources. Simply download and copy a directory called javaworld under your Web apps directory. Start Tomcat and point your browser to http://localhost:8080/javaworld/Test.jsp. Remember you will also need to download and install Fiorano's FioranoMQ to run those programs. Plus you'll have to create any queues and/or topics that you use in your JSP code. In my case, for example, I created a queue called ModiQueue and a topic called ModiTopic. Fiorano's administration tool facilitates that process.

Enhancements

The tags presented here provide the basic JMS functionality you need to get your feet wet. You can add enhancements such as the ones described below to increase the capability of those tags:

  1. Message producers -- message senders and publishers -- have control over many message characteristics in JMS. Those characteristics include the message time-to-live that determines how long a message -- if not delivered -- stays around until it is dropped, the priority of the message, and whether the message persists in permanent storage (at least more permanent than RAM). For simplicity, the custom tags introduced in this article do not control those characteristics. However, it is not difficult to expose those characteristics as attributes of the write tag similar to the destination attribute.
  2. JMS defines the concept of a transaction in both the point-to-point and publish-and-subscribe messaging models. Any application that deals with transferring data that should not be corrupted or lost should always do so under the umbrella of a transaction. As a result, both the write and read tags should be enhanced to optionally use a transaction if specified.
  3. The JMSQueueConnectionFactory and JMSTopicConnectionFactory classes contain JMS provider-specific code. Therefore, swapping out providers requires some coding. With some more design work, a properties file/XML file that allows new providers to be easily used can make those classes configurable. In essence, the provider-specific code is pulled out into yet another class, which has a well-defined interface (as defined by you). The properties file informs the JMSQueueConnectionFactory or JMSTopicConnectionFactory of which of those classes to use to obtain the connection factory. That is essentially the strategy pattern.

Conclusion

By using JSP content, developers can create powerful and dynamic Webpages and applications. When coupled with JMS, JSP becomes even more robust. Because the JSP architects have given us the strength of extending JMS with custom tags, using JMS with JSP is not difficult.

The next time you need to update an inventory database halfway across the world from your JSP page, and you don't want your JSP page to wait 30 minutes for the update to finish, don't worry. Just use the custom JSP tags that I created in this article, and welcome to the wonderful world of asynchronous programming with JMS!

Tarak Modi is an independent consultant and a certified Java programmer. He has a master's degree in computer engineering and an MBA concentrating in information systems. He has several years of proven experience working with C++, Java, and technologies such as DCOM, CORBA, RMI, and EJB. He has written articles for leading software magazines including JavaWorld and is currently working on a book on JMS with Manning Publications from which this article has been adapted. To find out more about the book visit http://www.manning.com/modi/index.html. Also, visit Tarak's home page: http://tmodi.home.att.net/.
1 2 3 Page 2
Page 2 of 3
How to choose a low-code development platform