ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


JavaServer Pages, 2nd Edition

JSTL 1.0: What JSP Applications Need, Part 3

by Hans Bergsten, author of JavaServer Pages, 2nd Edition
10/30/2002

Previous installments in this series gave you an overview of JSTL--the new specification of commonly needed JSP tag libraries--and showed you how to use the core, internationalization, and database JSTL actions, as well as how to use JSTL effectively in an MVC application. In this final installment, we'll look at how you can leverage the JSTL classes when you develop your own custom actions. To understand what I describe here, you need to be a Java programmer and also know a thing or two about how to develop JSP custom actions in general.

Accepting EL Expressions as Attribute Values

The JSTL specification introduces an Expression Language (EL) that can be used to set JSTL action attributes to values computed at runtime, as you have seen in the previous parts of this series. A common question is "Can I also use the EL for setting attribute values in my custom actions?" The answer is: yes and no.

A JSP 1.2 container doesn't know anything about EL expressions, so they are evaluated by code in the JSTL tag handlers. JSTL 1.0 doesn't define a public API for this evaluation code, so there's no way to let custom tag handlers do the same in a way that works with all JSTL implementations. You can, however, pick one JSTL implementation and code to its API. For instance, if you're willing to be dependent on the JSTL Reference Implementation (RI) developed in the Apache Taglibs project (see the Resource section), you can use this org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager method in your tag handler:

public static Object evaluate(String attributeName,
  String expression,
  Class expectedType,
  Tag tag,
  PageContext pageContext);

This method takes an EL expression and evaluates it in the specified PageContext and converts (coerces) the result to the specified expected type, according to the rules defined by the JSTL 1.0 specification. The other parameters are used to include details in a possible error message, such as the name of the custom action and the attribute where the invalid expression is used.

In This Series

JSTL 1.0: What JSP Applications Need, Part 2 -- Part 2 of our JSTL series focuses on internationization, localization, and database access.

JSTL 1.0: Standardizing JSP, Part 1 -- JSTL offers a set of standardized JSP custom actions to handle common tasks. This article, the first in a series, provides an overview and shows how to use the most common tags.

You must call this method in one of the main methods in the tag handler (e.g. doEndTag()), never in the attribute setter method. The reason for this is that the setter method may not be called every time the tag handler is used, as I describe in my article, "JSP 1.2: Great News for the JSP Community, Part 2" (in the Tag handler life cycle and instance reuse section).

Here's a tag handler that accepts an EL expression as the value of its name attribute:

package com.ora.jstl;

import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;

public class HelloTag extends TagSupport {
   private String nameEL;

   public void setName(String name) {
      nameEL = name;
   }

   public int doEndTag() throws JspException {
      String name = (String)
      ExpressionEvaluatorManager.evaluate("name", nameEL, 
          String.class, this, pageContext);
      if (name == null || name.length() == 0) {
         name = "World";
      }
      try {
         pageContext.getOut().write("Hello " + name + "!");
      }
      catch (IOException e) {}
      return EVAL_PAGE;
   }
}

Note that to compile and use this tag handler, you must have the JSTL RI ExpressionEvaluatorManager in the classpath; it's available in the standard.jar file that's part of the RI download.

Related Reading

JavaServer Pages
By Hans Bergsten

If you can hold your horses a bit and wait for JSP 2.0, you don't have to do anything in your tag handlers to accept EL expression attribute values. JSP 2.0 (currently at the Proposed Final Draft stage, expected to be released Q1 2003) will include a somewhat extended version of the EL and will evaluate EL expressions before calling tag handler attribute setter methods. Hence, EL expressions can be used with any tag handler that is declared in the TLD to accept a runtime value. JSP 2.0 will also accept EL expressions anywhere in the template text.

Developing JSTL-Style Conditional Custom Actions

The JSTL specification group realized that no matter how many custom actions JSTL defines, there will always be a need for application-dependent custom actions. We tried to make it as easy as possible to develop custom actions that integrate nicely with the actions defined by the JSTL specification by including a number of public interfaces and base classes in the specification. The following sections show you some examples of how to use these classes and interfaces, starting with custom conditional actions.

The generic <c:if> and <c:when> actions, using the Boolean value of an EL expression as the condition, work great in many scenarios, but not in all. For instance, say you want to conditionally add some content depending on the time of day. You could create your own bean with Boolean properties suitable for use in an EL expression, but a custom action like this may be more convenient to use:

<xmp:ifAfternoon>
   Sorry, we only accept delivery requests before noon.
</xmp:ifAfternoon>

It's very easy to implement such a custom action, thanks to the extendable JSTL classes. Here's the complete tag handler code for the <xmp:ifAfternoon> action:

package com.ora.jstl;

import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.jstl.core.ConditionalTagSupport;

public class IfAfternoonTag extends ConditionalTagSupport {
   public boolean condition() {
      Calendar now = new GregorianCalendar();
      return now.get(Calendar.AM_PM) == Calendar.PM;
   }
}

The tag handler class extends the JSTL javax.servlet.jsp.jstl.core.ConditionalTagSupport class, which provides implementations for all standard JSP tag handler methods and calls the condition() method implemented by the subclass to decide what to do. It also includes setter methods for var and scope attributes, so a subclass like the one shown here behaves just like the <c:if> action: if the condition() method returns true, the custom action's body is processed; if it doesn't have a body, the value can be saved as a Boolean in variable and scope specified by the var and scope attributes.

You may expect there to be a similar base class for developing custom actions to be used, the same as a <c:when> action within a <c:choose> block, but there isn't. The reason is this: to ensure that custom actions cannot interfere with the somewhat complex interaction between the <c:choose> action and its nested actions, only <c:when> and <c:otherwise> actions are allowed as direct children of the <c:choose> action. But you can combine your own conditional actions with a <c:choose> like this to get the same multiple-choice effect:

<xmp:ifAfternoon var="isAfternoon" />
<c:choose>
   <c:when test="${isAfternoon}">
     Good day!
   </c:when>
   <c:otherwise>
     Good morning!
   </c:otherwise>
</c:choose>

Simply save the custom conditional result, using the var attribute, and then use this result in the EL expression for a <c:when> action in the block.

Developing JSTL-Style Iteration Custom Actions

Developing a custom iteration action can also be simplified by extending a JSTL base class, and custom actions nested within a JSTL <c:forEach> action body have easy access to iteration status information through a JSTL interface.

Let's look at a custom iteration action first. The JSTL base class you can extend is javax.servlet.jsp.jstl.core.LoopTagSupport. All you really need to implement in the subclass are three methods: prepare(), hasNext(), and next(). This gives you iteration plus support for the same var and varStatus attributes as the JSTL <c:forEach> action. If you want to support the begin, end, and step attributes, the base class provides protected fields and validation methods, but you have to implement the setter methods yourself (since not all subclasses need them, and the details differ, depending on if EL expressions are allowed or not).

To see how you can extend the JSTL base class for your own iteration action, let's develop a custom action that iterates through all days in the current month. The tag handler looks like this:

package com.ora.jstl;

import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.jstl.core.LoopTagSupport;

public class ForEachDayTag extends LoopTagSupport {
   private Calendar calendar;
   private int previousMonth;

   public void prepare() {
      calendar = new GregorianCalendar();
      calendar.set(Calendar.DAY_OF_MONTH, 1);
      // Set to last day in previous month, since next() increments it
      calendar.add(Calendar.DAY_OF_MONTH, -1);
      previousMonth = calendar.get(Calendar.MONTH);
   }

   public boolean hasNext() {
      int currentMonth = calendar.get(Calendar.MONTH);
      int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
      int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
      return currentMonth == previousMonth || currentDay < lastDay;
   }

   public Object next() {
      calendar.set(Calendar.DAY_OF_MONTH, 
          calendar.get(Calendar.DAY_OF_MONTH) + 1);
      return calendar;
   }
}

The base class calls prepare() once, followed by a sequence of calls to hasNext() and next() until hasNext() returns false. The subclass code is pretty straight forward. The prepare() method creates a GregorianCalendar instance and sets it to the last day of the previous month. The hasNext() method returns true if the day currently represented by the calendar is either a day in the previous month (i.e., before the first iteration) or a day other than the last day of the current month. The next() method, finally, moves the calendar to the next day and returns the adjusted calendar.

Here's an example of how you can use this custom iterator to generate an HTML table with a cell for each day in the current month:

<table>
   <xmp:forEachDay var="curr">
      <tr>
         <td>
            <fmt:formatDate value="${curr.time}" 
               pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

It would be fairly easy to extend this custom action to support the begin, end, and step attributes, and maybe an attribute for setting the month to iterate over. I leave that as an exercise for you to try out on your own.

Using JSTL Iteration Status Info

What if you want to do things only for certain items in the body of an iteration action? The JSTL <c:forEach> action and custom actions extending the LoopTagSupport base class expose information about the current item through a variable named by the varStatus attribute. This variable is an instance of a bean with properties like first, last, index, and more (see the JSTL specification for details). For instance, you can use it like this to get alternating colors for the rows in a table:

<table>
   <xmp:forEachDay var="curr" varStatus="stat">
      <c:set var="bg" value="white" />
      <c:if test="${stat.index % 2 == 0}">
         <c:set var="bg" value="blue" />
      </c:if>
      <tr bgcolor="<c:out value="${bg}" />">
         <td>
           <fmt:formatDate value="${curr.time}" 
              pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

Sometimes it's impossible to use an EL expression testing the status bean properties (or the current item itself) to figure out if special processing is needed or not. With the calendar iterator, for instance, you can't use an EL expression to find out what day in the week the current item represents. This is where a custom action specifically intended for use within an iterator action body can come in handy.

A custom action can use the knowledge that a JSTL iterator action implements the javax.servlet.jsp.jstl.core.LoopTag interface to get access to the current item and the iteration status iformation. Here's the tag handler code for a custom action that processes its body only if the current item represents a Sunday:

package com.ora.jstl;

import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.jstl.core.ConditionalTagSupport;
import javax.servlet.jsp.jstl.core.LoopTag;

public class IfSundayTag extends ConditionalTagSupport {
   public boolean condition() throws JspTagException {
      LoopTag parent = 
          (LoopTag) findAncestorWithClass(this, LoopTag.class);
      if (parent == null) {
         throw new JspTagException("ifSunday must be used in loop");
      }
      Calendar current = (Calendar) parent.getCurrent();
      return current.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
   }
}

The LoopTag interface declares two methods: getCurrent() returns the current iteration item as an Object and getLoopStatus() returns an instance of LoopStatus (the same type as for the object exposed as the varStatus variable). The interface is implemented by the LoopTagSupport base class, so all tag handlers that extend this class get the correct behavior for free.

In this example tag handler, the parent that implements the LoopTag interface (our ForEachDayTag tag handler) is located using the findAncestorWithClass() method and the current item is retrieved by calling the parent's getCurrent() method. If the current item represents a Sunday, the condition() method returns true. With this custom action, it's easy to do whatever you want with Sundays:

<table>
   <xmp:forEachDay var="curr">
      <c:set var="bg" value="white" />
      <xmp:ifSunday>
         <c:set var="bg" value="red" />
      </xmp:ifSunday>
      <tr bgcolor="<c:out value="${bg}" />">
         <td>
            <fmt:formatDate value="${curr.time}" 
               pattern="EE dd, MMM yyyy" />
         </td>
      </tr>
   </xmp:forEachDay>
</table>

A custom action that needs to do something only for the first or last iteration, or perhaps only for every second or third iteration, can use the getLoopStatus() method to get the information it needs.

Using JSTL Classes to Produce Localized Text

There's one more JSTL class that you may find useful when you develop custom actions: the javax.servlet.jsp.jstl.fmt.LocaleSupport class. This class provides methods for getting localized messages from a ResourceBundle, using the same algorithms as the JSTL i18n actions for determining the appropriate locale (as I described in part 2 of this article series).

The class provides the following methods:

public static String getLocalizedMessage(PageContext pc, 
   String key);
public static String getLocalizedMessage(PageContext pc, String key, 
   String basename);
public static String getLocalizedMessage(PageContext pc, String key,
   Object[] args);
public static String getLocalizedMessage(PageContext pc, String key, 
   Object[] args, String basename);

The first two methods get a simple localized message for the specified key. The second method uses the specified basename to locate the correct ResourceBundle, while the first one uses the bundle selected for the current localization context. The second pair of methods are for parameterized messages, using the args parameter to set the message parameters.

Conclusion

If you've read all parts of this article series, you have a glimpse of what JSTL 1.0 has to offer, whether you're a page author or a programmer. I've covered all features except the JSTL XML processing tag library; it works pretty much the same as the other libraries and if you know XML and XPath, I'm sure you can figure out how to use it on your own. If you don't know XML and XPath, that's where you need to start, and I'm afraid that's out of scope for this article.

While you can get an idea about the possibilities from reading an article, the only way to really learn how to use a technology is to do just that: use it! The Resouce section gives you some pointers to where you can find out more about JSTL and where to ask questions. I hope you'll find JSTL both fun and useful.

Resources

Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.