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:
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.
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:
- 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. - 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:
- 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.
- The Web server provides me with deterministic destruction by guaranteeing to call the
destroy()
method during shutdown (unless the Web server crashes). - The JSP content developer does not have to perform any extra initialization or cleanup work.
- 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:
- 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. - 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
andread
tags should be enhanced to optionally use a transaction if specified. The
JMSQueueConnectionFactory
andJMSTopicConnectionFactory
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 theJMSQueueConnectionFactory
orJMSTopicConnectionFactory
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!
This story, "Add the power of asynchronous processing to your JSPs" was originally published by JavaWorld.