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


JSP 1.2: Great news for the JSP Community, Part 1

by Hans Bergsten
10/10/2001

This article is the first installment of a two-part article. Part One, presented here, covers the new features in JSP 2.1; Part Two offers details about the changes and additions in the custom tag handler API.

Ever since Sun released the JavaServer Pages (JSP) 1.1 specification in December 1999, thousands of developers have flocked to this powerful technology for dynamic Web content development. JSP-INTEREST is now Sun's second most popular mailing list, with more than 4,000 subscribers; new Web sites dedicated to JSP pop up every day; and some 30 JSP books have been released so far. JSP 1.1 is supported by most, if not all, Web servers and application servers, and developers can take advantage of numerous JSP tag libraries and frameworks -- of both the open source and the commercial varieties.

(For more on JSP, Servlets, and Tomcat, see ONJava's JSP and Servlets topic page.)

While Web application developers all over the world have been busy developing new applications based on JSP 1.1, the JSP 1.2 specification has been taking shape within the Java Community Process group called JSR-053. Members of this group include authors, application server developers from big and small commercial vendors as well as the open source community, and servlet and JSP developers.

Released September 17th, the JSP 1.2 specification is now ready for public consumption. It contains everything from brand new features to corrections and clarifications of areas that were not quite right in the previous version. The most important changes:

Tomcat 4.0 is the reference implementation for the JSP 1.2 and Servlet 2.3 specifications. The Tomcat server is a Java-based Web application container that was created to run Servlet and JavaServer Page Web applications. Tomcat 4.0 was released at the same time as the JSP 1.2 spec, so you can try out the new features now. Some of the commercial Web containers, such as New Atlanta's ServletExec 4.1, already support the new specifications as well.

This article will cover all of the new stuff the specification group has cooked up and show you how to use the new features. Most of the changes apply to programmers and container developers, so this article assumes you're a Java programmer familiar with the JSP 1.1 specification. If you're primarily a page author, I'm afraid there's not much of interest for you in this article, but rest assured that you will benefit from JSP 1.2's more powerful custom tag libraries, more efficient containers, and better portability between containers.

Servlet 2.3 and Java 2

JSP 1.2 is based on the latest Servlet specification: Servlet 2.3. This gives a JSP 1.2 application access to all new servlet features, such as listeners, filters, and internationalization improvements. Later in this article, I describe how you can use listeners in a JSP tag library. I also recommend that you read Jason Hunter's Servlet 2.3 articles in JavaWorld (see the Resources section) to learn how to take advantage of the other new Servlet features.

Both JSP 1.2 and Servlet 2.3 require the Java 2 platform. The good news is that Web containers can now take advantage of all the new features added since JDK 1.1, such as collections, more advanced class loaders, and flexible security. The same is, of course, true for your own Web applications. The bad news is that JSP 1.2 applications will not run on platforms that only support JDK 1.1. There are very few of these left at this point, so this should not be a problem for most of us.

JSP 1.2 is backwards compatible with JSP 1.1. If you have a JSP 1.1 application, it should run without any tweaking in a JSP 1.2- compliant container.

Include Without Flushing

Comment on this articleWhat are your thoughts on the new JSP 1.2 specification?
Post your comments

In JSP 1.1, the flush attribute was mandatory and only accepted the value true. This meant that as soon as you used the include action, all content generated up to that point was flushed to the browser, and you could no longer forward to another page or set response headers. This caused a lot of confusion and side-effect problems that were hard to understand and deal with. Readers of the JSP-INTEREST mailing list, or any other JSP forum, know what I'm talking about.

JSP 1.2 takes advantage of improvements in the Servlet 2.3 specification to finally get rid of this restriction. The flush attribute of the include action now accepts the value false. In fact, the attribute is now optional and false is the default value, so in most cases you can just omit it and forget about all the flushing issues of the past.

With JSP 1.1, a page like this resulted in an error due to the use of a forward after an include:

<jsp:include page="common.jsp" flush="true" /> 
<% if (someCondition) { %> 
  <jsp:forward page="another.jsp" /> 
<% } %>

In JSP 1.2 it's legal to change the flush attribute value to false or omit it.

You can even use the include action in the body of a custom action element:

<xsl:apply xsl="style.xsl"> 
  <jsp:include page="someXML.jsp" /> 
</xsl:apply>

This was also a no-no in JSP 1.1 due to buffering issues that have been resolved in JSP 1.2.

JSP Pages, JSP Documents and XML Views

An important part of JSP 1.2 is the definition of an XML syntax for JSP. A JSP 1.2-compliant container must accept files in both the old JSP 1.1 format, now formally called a JSP Page, and the new XML format, formally called a JSP Document.

A JSP Document has a root element named <jsp:root> that defines the namespaces for the standard JSP elements and all custom tag libraries used in the page. All JSP directives and scripting elements must be represented by XML elements instead of the ASP-like elements used in a JSP Page:

<jsp:root 
 xmlns:jsp="http://java.sun.com/JSP/Page" 
 xmlns:demo="/demolib" 
 version="1.2"> 
 
 <jsp:directive.page contentType="text/html" /> 
 
 <ul> 
  <demo:myLoopTag items="myCollection" var="current"> 
   <li> 
     <jsp:getProperty name="current" property="lastName" />, 
     <jsp:getProperty name="current" property="firstName" /> 
   </li> 
  </demo:myLoopTag> 
 </ul> 
 
</jsp:root>

Besides JSP elements, such as the declaration and custom actions shown in this example, a JSP Document can also contain XML elements, such as XHTML elements. Examples here are the <ul> and <li> elements.

It's not in the scope of this article to describe the complete JSP Document syntax. Note, however, that it's much more verbose than the JSP Page syntax, and is primarily intended to be used by tools. If you still want to use this syntax, I recommend that you read section 5, JSP Documents, of the JSP 1.2 specification.

When a container receives a request for a JSP Page the first time, it converts it to an XML document, similar to a JSP Document, before it performs validation and conversion to a servlet. This XML document is formally called the XML View of the page. The only difference between an XML View and a JSP Document is that all template text is wrapped within <jsp:text> elements, using CDATA sections to prevent problems with special characters that may be used in the template text:

<jsp:root 
 xmlns:jsp="http://java.sun.com/JSP/Page" 
 xmlns:demo="/demolib" 
 version="1.2"> 
 
 <jsp:directive.page contentType="text/html" /> 
 
  <jsp:text><![CDATA[<ul> 
]]></jsp:text> 
    <demo:myLoopTag items="myCollection" var="current"> 
    <jsp:text><![CDATA[<li> 
]]></jsp:text> 
    <jsp:getProperty name="current" property="lastName" /> 
      <jsp:getProperty name="current" property="firstName" /> 
       </demo:myLoopTag> 
      <jsp:text><![CDATA[</ul> 
]]></jsp:text> 
 
</jsp:root>

The new TagLibraryValidator described later in this article uses the XML View to validate a JSP Page.

Tag Library Deployment and Distribution News

With JSP 1.2 also comes a couple of simplifications for deployment and distribution of tag libraries: auto-discovery of tag libraries and distribution of multiple libraries in one JAR file.

Auto-Discovery of Tag Libraries

In JSP 1.1, you could refer to a tag library by letting the taglib directive's uri attribute specify either the real path to the tag library or a symbolic name. If you used a symbolic name, you also had to map the name to the real path with the <taglib> element in the web.xml file for the application. JSP 1.2 adds a third, more powerful alternative: just drop the tag library JAR file in the WEB-INF/lib directory and use the library's standard URI in the taglib directive.

Here's how it works. The Tag Library Descriptor (TLD) includes a <uri> element that you use to define the standard URI for the library:

<taglib> 
  ... 
  <uri>/demo</uri> 
  ... 
</taglib>

When the Web application is started, the container scans through the WEB-INF directory structure for files ending with the extension .tld and all JAR files that contain files with the .tld extension in their META-INF directory. In other words, all TLD files. For each TLD, the container looks for the <uri> element and creates a map from the URI to the TLD that contains it. In your JSP page, you just have to place a taglib directive with a uri attribute value matching the URI in the TLD:

<%@ taglib uri="/demo" prefix="demo" %>

That's it! No more messing around with real paths (which may change, for instance, when you upgrade the library) in the JSP pages or web.xml tag library declaration woes.

Multiple Libraries in One JAR File

A beneficial side effect of the auto-discovery feature is that you can bundle more than one tag library in the same JAR file. In JSP 1.1, a TLD contained in a JAR file had to be named exactly META-INF/taglib.tld. This meant that a JAR file could only contain one TLD.

The auto-discovery feature, however, treats any file with a .tld extension in a JAR file's META-INF directory as a TLD. You can therefore put multiple TLDs (along with the class files for the libraries) in one JAR file. This makes it easier for your users to deploy related tag libraries. Note that you must use the auto-discovery mechanism to deploy multi-library JAR files, since there's no way to specify the path to an individual TLD in such a JAR file.

Tag Library Event Listeners

The Servlet 2.3 specification extends the event listener capability of previous versions. Previously, you've only been able to receive notifications about changes to session attributes, but starting with Servlet 2.3 you can implement listeners for servlet context and session life cycle events, servlet context attribute changes, and session activation and passivation events (used by a container that temporarily saves session state to disk or migrates a session to another server).

All new types of listeners follow the standard Java event model. In other words, a listener is a class that implements one or more of the new listener interfaces. The interfaces define methods that correspond to events. Your listener class is registered with the container when the application starts, and the container calls the event methods at the appropriate times.

You can bundle one or more event listeners with a tag library. To get the listeners registered, you just define the listener implementation classes in the Tag Library Descriptor (TLD) for your library using the new <listener> element:

<listener> 
  <listener-class>com.foo.MyListener</listener-class> 
</listener>

When the container loads the Web application, it looks through all TLDs for listener definitions and registers them.

You can use listeners for a number of tasks. For instance, a servlet context lifecycle event listener can initialize resources used by the application (such as a connection pool) when the application starts and shut them down gracefully when it stops. A session lifecycle listener can initialize new sessions or keep track of the number of active sessions.

Here's an example of a session lifecycle listener that keeps track of active sessions:

  package com.foo; 
 
import javax.servlet.*; 
import javax.servlet.http.*; 
public class MyListener implements HttpSessionListener { 
  public void sessionCreated(HttpSessionEvent hse) { 
        int[] counter = getCounter(hse); 
        counter[0]++; 
  } 
 
  public void sessionDestroyed(HttpSessionEvent hse) { 
    int[] counter = getCounter(hse); 
      counter[0]--; 
  } 
 
  private int[] getCounter(HttpSessionEvent hse) { 
    HttpSession session = hse.getSession(); 
    ServletContext context = session.getServletContext(); 
    int[] counter = (int[])  
                  context.getAttribute("com.foo.counter"); 
    if (counter == null) { 
        counter = new int[1]; 
        context.setAttribute("com.foo.counter", counter); 
    } 
    return counter; 
  } 
}

For every new session, a counter maintained as a servlet context attribute is incremented. When a session ends, the counter is decremented. Tags in the library can be used to simply display the current number of active session, reject new users if a certain number of sessions are active (to ensure optimal performance), or whatever makes sense in your application.

Validators

A brand new feature in JSP 1.2 is the tag library validator, represented by the javax.servlet.jsp.tagext.TagLibraryValidator class. The Web container uses the validator defined for a tag library when it converts a JSP page that uses the tag library to a servlet, after performing validation of the page based on the information available in the TLD (mandatory attributes, empty bodies). You implement a validator as a subclass of TagLibraryValidator and override the validate() method:

public ValidationMessage[] validate(String prefix,  
       String uri, PageData pageData)

The validator's validate() method is invoked with an instance of PageData. Through the PageData instance, the validator can get the XML representation of the page. This is either an XML View for a page written in the JSP Page format or a JSP Document (written from scratch using the JSP XML syntax). With access to the XML representation of the complete page, a validator can perform validation that would be impossible using just TLD information or the information about a single custom action element available to a TagExtraInfo class. For instance, a validator can verify that a custom action that must be used as a subelement of another action element is not used anywhere else, or that action elements are used in the appropriate order.

A validator is associated with a tag library through the new <validator> element in the TLD:

<validator> 
  <validator-class>com.foo.MyValidator</validator-class> 
</validator>

The <validator-class> element is used to specify the validator class name. Optional <init-param> elements can be used to configure a generic validator for a specific tag library.

Validation in Action

Here's an example of a validator that verifies that a <param> custom action element is only used within the body of a <redirect> action element from the same tag library. We look at the code piece by piece, starting with the class declaration:

package com.foo; 
 
import java.util.*; 
import javax.servlet.jsp.tagext.*; 
import org.jdom.*; 
import org.jdom.input.*; 
public class MyValidator extends TagLibraryValidator { 
 private SAXBuilder builder = new SAXBuilder(); 
 private Namespace jspNamespace = Namespace.getNamespace("jsp", 
   "http://java.sun.com/JSP/Page");

As you can see, the validator class extends the TagLibraryValidator class that is part of the JSP API. In this particular validator, I use JDOM to work with the XML representation of the page. I therefore import the JDOM packages containing classes for parsing and the JDOM tree. You can, of course, use any XML parser and validation tools you want. For instance, the Jakarta Taglibs project is experimenting with a lot of different XML tools in their validators.

I create an instance of the JDOM SAXBuilder class and save it as an instance variable. If the container caches instances of validators, this saves me from having to create this object for every page that's validated. I also create a JDOM Namespace for the JSP namespace as an instance variable. More about that later.

The validator must override the validate() method:

public ValidationMessage[] validate(String prefix, String uri, PageData pd) {
  
  ValidationMessage[] vms = null;
   ArrayList msgs = new ArrayList();
   try {
   Document doc = builder.build(pd.getInputStream());
   Element root = doc.getRootElement();
   validateElement(root, prefix, msgs);
   }
   catch (Exception e) {
   vms = new ValidationMessage[1];
   vms[0] = new ValidationMessage(null, e.getMessage()); }
  
   if (msgs.size() != 0) {
   vms = new ValidationMessage[msgs.size()];
   msgs.toArray(vms);
   }
   return vms;
  }

The validator() method gets the XML representation of the page and uses JDOM to parse the document. It then calls the validateElement() method with the document's root element, the prefix used for this tag library, and an ArrayList used to collect error messages. If the validateElement() method finds any errors, the message list is converted to an array of ValidationMessage instances, used as the return value.

As you will see later, a ValidationMessage instance contains the error message itself and possibly information about where the error was found in the JSP source file. The fact that the validate() method returns an array of ValidationMessage instances means that all errors found in the page can be presented in one shot, allowing the page author to fix them all at once instead of one by one.

The validateElement() method is a dispatcher to methods that validate specific elements:

private void validateElement(Element e, String ns, ArrayList msgs) {
  
  if (ns.equals(e.getNamespace().getPrefix())) {
    if (e.getName().equals("param")) {
        validateParam(e, ns, msgs);
    }
  }
  if (e.hasChildren()) {
    List kids = e.getChildren();
    Iterator i = kids.iterator();
    while(i.hasNext()) {
        validateElement((Element) i.next(), ns, msgs);
    }
  }
}

It's a recursive method that is called for all elements in the document tree. First it checks if the current element is part of the namespace for this tag library. It then checks if it's an element that needs to be validated, and if so, calls the appropriate method.

In this example I only validate elements of type param, but you can see how you could extend it to validate other elements as well.

For all types of elements that have child nodes, the validateElement() method calls itself again with each child node. That's how the method recursively scans the whole document tree.

The real validation code in this example is found in the validateParam() method:

private void validateParam(Element e, String ns,  
  ArrayList msgs) { 
    Element parent = findParent(e, ns, "redirect"); 
      if (parent == null) { 
        String id = e.getAttributeValue("id", jspNamespace); 
        ValidationMessage vm = new ValidationMessage(id, 
          e.getQualifiedName() +  
          " must only be used with 'redirect'"); 
          msgs.add(vm); 
      } 
    }

The validateParam() method uses the findParent() method to see if the current param element has a parent element of type redirect. If it doesn't, it means that the param element is used incorrectly. In this case, a ValidationMessage instance is created to report the error and added to the list of error messages.

A ValidationMessage contains two pieces of information: the error message itself and a unique ID for the element that the message refers to. The unique ID is assigned by the container and provided to the validator as an element attribute named id in the JSP namespace; in other words, an attribute named jsp:id.

Therefore, the first thing the validateParam() method does if it finds an error is to try to get this attribute so it can include it in the ValidationMessage. This is where the Namespace instance variable mentioned earlier is used.

The container maintains a map between the ID and the location (line and column) of the element in the JSP source file. With this information, it can generate user-friendly error messages, including the location of the error. Note, however, that the container is not required to add the unique ID attributes. For instance, Tomcat 4.0 does not support this feature but future versions may.

Finally, the findParent() method looks like this:

private Element findParent(Element e, String ns, String name) {
    if (e.getName().equals(name) &&
        ns.equals(e.getNamespace().getPrefix())) {
        return e;
        }
        Element parent = e.getParent();
        if (parent != null) {
            return findParent(parent, ns, name);
        }
        return null;
  }

It simply calls itself recursively until it either finds an element of the specified type or reaches the top of the document tree. If it finds a matching element, it returns it. Otherwise it returns null.

Related Reading

JavaServer PagesJavaServer Pages
By Hans Bergsten
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

JSP 1.2 still supports the validation of individual custom action elements through the TagExtraInfo isValid() method in addition to the new validation mechanism. The validation that can be performed by the isValid() method is so limited compared to what a validator can do, however, I recommend that you focus on validators from now on.

Conclusion

In this installment, I have shown a number of new features introduced by the JSP 1.2 specification that will certainly improve your applications. You can take advantage of everything in the Java 2 platform and the Servlet 2.3 API, use the include action in a much more flexible way than before, deploy tag libraries without additional configuration, use event listeners to simplify application and session state management, and create validators for your tag libraries for complete validation of JSP pages with author-friendly error messages.

But this is only the beginning. In the second part of this article I tell you all the good stuff that's been added to the custom tag developer's toolbox.

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.