Most JavaServer Pages (JSP) developers that I've worked with love JSP because it's easy to use and a powerful way to design flexible Webpages. As passionate Web designers, most JSP developers are not interested in the details of n-tier architectures and distributed programming, let alone the intricacies and complexities of asynchronous processing with Java Messaging Service (JMS). Nevertheless, circumstances will present themselves when JSP developers may need the power of asynchronous processing. For example, suppose an online Webstore written completely with JSP needs to update its inventory after each processed sale, yet the inventory management system is across the globe. In that case, using JMS to update the inventory asynchronously may provide an attractive alternative. Situations similar to that one most likely fueled Sun to make both JSP and JMS key pieces of the Java 2 Platform, Enterprise Edition (J2EE).
So, is the answer as simple as making JSP developers master JMS? Unfortunately not. As I mentioned above, this answer will probably displease many JSP developers. Then, what is the solution?
JSP 1.1 introduced an extremely valuable new capability: the ability to define your own JSP tags, also called custom tags. You can group custom tags into tag libraries and reuse them in any number of JSP files. Custom tags allow complex programming logic to boil down to a set of simple tags, which JSP developers can easily use to develop content. Although custom tags require a bit more effort to set up than normal tags, the benefits definitely outweigh the costs. Tag libraries increase productivity by encouraging labor division between library developers and library users. Developers expert in accessing data and other services create JSP tags; Webpage authors who focus on the design of user interfaces use them.
In this article, I will design and implement two custom JSP tags that will allow JSP developers to work with JMS in the form that they are most comfortable with -- tags. Plus, since all the JMS programming logic is buried deep within the custom tags, JSP developers do not need to learn JMS. That's what I call killing two birds with one stone.
For example, using the write
custom tag that you will create in this article, a JSP developer can send/publish a message as shown below:
<jms:write destination="jms://Queue/ModiQueue" message="Hello World"/>
and can receive a message as follows:
<jms:read destination="jms://Queue/ModiQueue"/>
Background on JMS
To understand JMS, you must understand message-oriented middleware (MOM), which has grown in popularity over the last few years. Let's take a look at two key characteristics of MOM that differentiate it from the more traditional middleware based on remote procedure call (RPC).
- MOM allows asynchronous communication between applications: When you send a letter to your friend via snail mail, you obviously do not wait for his response before doing anything else. You go on with your life. At some point, you may receive a response to your letter but, in the meantime, your actions do not depend upon it. That example demonstrates the basics of asynchronous communication and, hence, the basics of how MOM works.
- MOM allows the communicating applications to have completely separate lifetimes: Consider the following scenario: A salesperson fills in customer orders on his laptop while flying to the corporate office. As the laptop is not connected to the central computer, it cannot process the orders at that time. However, both the laptop and the central computer are equipped with MOM software. Though the laptop cannot communicate with the central computer at the time the salesperson enters the orders, the MOM on the laptop keeps track of those order messages. Once the salesperson is back in his office and hooked up to the corporate intranet, the laptop's MOM fires off those stored messages to the MOM software on the central computer, which receives and processes those messages. The MOM ensures that the messages are not lost, delivered out of sequence, or duplicated. That second aspect of MOM makes it even more critical to successful distributed applications.
Those characteristics make MOM a popular alternative to RPC, which fails to offer either of those capabilities.
So, what does JMS have to do with MOM? JMS provides a standard Java-based interface to MOM's message services. My simplified definition of JMS is as follows: JMS is an API used to access MOM facilities from a Java application.
Figure 1 shows the pictorial form of the above definition.
JMS supports two popular messaging styles: point-to-point messaging and publish-and-subscribe messaging.
Point-to-point messaging
In point-to-point messaging, two applications use MOM to communicate with each other, often as an asynchronous replacement for RPC. As shown in Figure 2, JMS allows multiple senders, but only one receiver in this model. A queue, which channels the messages, forms the central concept of point-to-point messaging. An application interested in sending a message begins with a queue connection factory that obtains a queue connection. That queue connection then creates a queue session, the application's personal window into the connection. A JMS client uses that session to create a message producer, which sends messages to the queue.
On the other end, the receiving application goes through a similar sequence of obtaining a queue connection factory, a queue connection, and a queue session. The receiving application's functions differ from the sending application in that it uses the session to create a message consumer to receive messages from the queue.
Publish-and-subscribe messaging
When multiple applications need to receive the same message(s), they use the publish-and-subscribe model. Figure 3 illustrates that model, where a many-to-many relationship exists between the message producers and the message consumers. Note, as Figure 3 illustrates, that a topic forms the central concept in that model. Instead of message senders and receivers, you have message publishers and subscribers.
A publisher uses a topic connection factory to create a topic connection. It then uses that topic connection to create a topic session, which provides the publisher with a personal window into the topic connection. The publisher can now use the topic session to create a message producer, which publishes messages to the topic.
On the other end, the subscribing application goes through a similar sequence of obtaining a topic connection factory, a topic connection, and a topic session. The subscribing application differs from the publisher in that it uses the session to create a message consumer that subscribes to messages from the topic.
Although I've glossed over a number of issues regarding JMS usage, what I've included above is all you need to know about JMS to create the custom JSP tags discussed in this article.
The JSP custom tag architecture
Many developers have written articles on creating and using custom tags, so I won't spend much time introducing the basics of JSP custom tags. In addition, Sun provides a sample chapter on creating custom JSP tags from the book Core Servlets and JavaServer Pages, by Marty Hall. See the Resources below for references to this and other sources that cover creating custom JSP tags.
In general, a custom tag consists of three pieces:
- A tag library descriptor that maps the XML tag -- JSP -- to its implementation -- the tag handler class. For those of you familiar with CORBA, that step resembles creating IDL interfaces.
- A tag handler class that defines the tag's behavior. When the Web server comes across a custom tag, it relies on the tag's handler class to complete all the work. In CORBA, that step is comparable to coding the CORBA servers that implement the IDL interfaces defined in Step 1 above.
- JSP files that use the custom tag. In CORBA, that would be analogous to creating clients that use CORBA servers' services.
The tag developer creates the first two pieces only once. The JSP developer creates the last piece while designing the content and (re)uses the first two pieces.
Step 1: Define the tags
In this article, you will create two custom tags: write
and read
.
Write
The first tag, write
, is for sending/publishing messages to a destination (i.e., a queue or topic). The definition of this tag is shown below:
<tag> <name>write</name> <tagclass>JmsWriteTag</tagclass> <info>Send/Publish a message</info> <attribute> <name>destination</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>message</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
The above tag definition provides the following information:
- The tag name is
write
. - The tag handler class is
JmsWriteTag
, which is thetagclass
element's value.JmsWriteTag
must be the fully qualified class name of the tag handler implementation. I cover handler classes in the following section. - The tag has two attributes.
destination
is a mandatory attribute;message
is an optional attribute. - Both the attributes can be JSP expressions of the form
<%= expression %>
, since thertexprvalue
element's value is true for both attributes.
The format of the destination
attribute's value is especially noteworthy. Examine again the example I introduced earlier:
<jms:write destination="jms://Queue/ModiQueue" message="Hello World"/>
Note the format of the destination
attribute; it looks like a URL. Its value comprises three parts:
- The constant
"jms://"
- The constant queue or topic, depending on the messaging model desired
- The name of the queue or topic to use
You can specify the message to be sent/published in two ways. The first, which is obvious from the above definition, is via the message
attribute. You can also specify the message through the tag body. For example:
<jms:write destination="jms://Queue/ModiQueue"> Hello World </jms:write>
Note that in the latter case, I did not specify the message
attribute. If I had done so, the tag handler would have ignored the body. Why? Because that's the way I coded the handler for that tag. I'll discuss the code later.
Read
The second tag, read
, receives messages from a destination. Its definition follows:
<tag> <name>read</name> <tagclass>JmsReadTag</tagclass> <info>Receive a message</info> <attribute> <name>destination</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
The above definition is a simpler version of write
's. The handler implementation for the read
tag is JmsReadTag
. It only has one mandatory attribute, destination
. Below you will find an example of how to use the read
tag:
<jms:read destination="jms://Queue/ModiQueue"/>
Listing 1 shows the entire tag library description.
Listing 1: Tag library description
Step 2: Implement the tags
Now that you've defined the tags, it's time to actually implement them. First, take a look at the tag handler class for the write
tag. When the Web server comes across the write
tag, it will call the tag handler class to complete all the work. As specified by JSP, a handler class must implement the javax.servlet.jsp.tagext.Tag
interface by making the handler extend either the javax.servlet.jsp.tagext.TagSupport
class or the javax.servlet.jsp.tagext.BodyTagSupport
class. If the handler implementation needs to manipulate the tag body -- as it does in this case -- it needs to extend the BodyTagSupport
class.
After creating a new instance (or reusing an old instance from a pool) of a handler class, the Web server informs the handler of all specified attributes. The handler class must follow the JavaBeans standard of naming property modifiers; that is, for an attribute called destination
, the handler class must have a setDestination()
method defined as follows:
public void setDestination(String destination);
Accordingly, the handler implementation defines a setter method for each attribute. The handler also overrides the doAfterBody()
method of the BodyTagSupport
base class. The Web server calls that method when it is ready to process the custom tag's body. The implementation of doAfterBody()
checks to see whether the message
attribute has been specified and, if so, ignores the body. A message specified using the message
attribute takes precedence over a message specified in the body of the write
tag.
If no message
attribute was specified, then the body becomes the message as shown below:
// Was a message specified via the message attribute? if( message == null ) { // No. // Get the body content BodyContent body = getBodyContent(); // Set the message equal to this content. message = body.getString(); }