Skip to main content

Validating Custom Tags at Translation Time

February 4, 2004

{cs.r.title}








Contents
Custom Tag Examples
Custom Tag Validation with TEI
Taglib Validation with TLV
JSTL TLVs
Conclusion

In order to create a library of custom tags (a taglib), we need to follow at least two steps:

  • Develop classes that implement the tags (tag handlers).
  • Create a Tag Library Descriptor (TLD) file.

The TLD defines properties for the taglib as a whole (such as its URI and description) and for its individual tags (such as name and attributes). It can also define validation rules that dictates how the tags must be used: if a JSP page using the taglib does not follow the rules, it will not be compiled.

Some of these rules are well known to taglib developers:

  • The tag's body can be empty or it can contain JSP code or tag-dependent code. This rule is defined by the tag's <body-content> element.
  • A tag attribute might be required or not (defined by the attribute's <required> element).
  • An attribute value can be a runtime expression (defined by the attribute's <rtexpvalue> element).

Besides these "popular" rules, the JSP specification allows two other types of compile-time validation:

  • Extra validation for a custom tag through a Tag Extra Info (TEI).
  • Validation for the JSP document using the taglib through a Tag Library Validator (TLV).

In this article we explain how to use these two validation techniques.

Custom Tag Examples

In order to explain how these validations work, let's first create a set of custom tags to be used throughout the article.

Our first tag is called today and it displays the current date, formatted according to a given locale. The locale can be set by two different ways:

  • By the attribute localeCode, which is a String object that represents the locale's language and country (like en_US or pt_BR).
  • By passing an existing java.util.Locale object through the attribute locale.

Here is a JSP page example using this tag:

<%@ taglib prefix="helper"
    uri="http://felipeal.net/tags/helper.tld"%>
<%@ page import="java.util.Locale" %>

Today in Brazil: <br>
<br>
<helper:today localeCode="pt_BR"/><br>
<br>

Today in the US:<br>
<br>
<helper:today locale="<%=Locale.US%>"/><br>
<br>

And its output:

Today in Brazil:

Terça-feira, 27 de Janeiro de 2004

Today in the US:

Tuesday, January 27, 2004

The other tag is called pageGuard and it is used to guarantee that a page can only be accessed by logged users with permission to do so (similar to the page-guard tag described in Core J2EE Patterns). As this is a site-specific tag, it belongs to a taglib called mySite. Also, it has only one attribute, role, which is a string representing a role the logged user must have to access the page (how a logged user is represented is beyond the scope of this article). The idea here is that all HTML and JSP code in every page of our site is surrounded by this tag, as shown above:

<%@ taglib prefix="mySite" %>
    uri="http://felipeal.net/tags/mySite.tld"

<mySite:pageGuard role="admin"/>
<html>
<!-- HTML and JSP code goes here -->
</html>

Here are the TLD fragments that define these two custom tags:

<tag>
  <name>today</name>
  <tag-class>article.tags.TodayTag</tag-class>  
  <body-content>EMPTY</body-content>
  <description>
    Display current time according to a locale
  </description>
  <attribute>
      <name>localeCode</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
      <name>locale</name>
      <required>false</required>
      <rtexprvalue>true</rtexprvalue>
  </attribute>
</tag>
<tag>
  <name>pageGuard</name>
  <tag-class>article.tags.PageGuardTag</tag-class>  
  <body-content>EMPTY</body-content>
  <description>
    Checks for authorization to access
    a page's content
  </description>
  <attribute>
      <name>role</name>
      <required>true</required>
      <rtexprvalue>false</rtexprvalue>
  </attribute>
</tag>

Custom Tag Validation with TEI

Let's go back to our first tag, today. That tag requires a Locale object, which must be set either by the locale or localeCode attributes. But what would happen if both attributes are set -- or none at all? In the former case, the behavior would depend on the tag handler's implementation; in the latter, it would probably raise a NullPointerException.

What should we do to force usage of only one of these attributes? We cannot explicitly define this kind of validation in the TLD, but we can do it implicitly through a Tag Extra Info (TEI), which is defined by the tei-class element in the TLD:

<tag>
  <name>today</name>
  <tag-class>article.tags.TodayTag</tag-class>  
  <tei-class>article.tags.TodayTEI</tei-class>
  <body-content>EMPTY</body-content>
<-- other attrs. omitted as they didn't change -->
</tag>

To create a TEI, we must extend javax.servlet.jsp.tagext.TagExtraInfo and override the method boolean isValid(TagData data), where TagData represents the translation-time information about the tag attributes and their values. In our case, we can get a Enumeration with all of the attributes currently set in the tag and return true only if just one of the attributes (locale or localeCode) is being used:

public boolean isValid( TagData tagData ) {
   
  Enumeration enum = tagData.getAttributes();
  // flag indicating if the locale was set
  // (through localeCode or locale)
  boolean hasLocale = false;
 
  while ( enum.hasMoreElements() ) {
    String attribute =
           (String) enum.nextElement();
    if ( attribute.equals("locale") ||
         attribute.equals("localeCode") ) {
      if ( ! hasLocale ) {
        // found the first attribute
        hasLocale = true;
      } else {
        // found the 2nd attr.: invalid usage
        System.err.println(
          ">>>>>>>> Date tag has both locale" +
          "and localeCode attributes" );
        return false;
      } // if ! hasLocale
      } // if equals
  } // while
   // if no attribute was found, returns false
  if ( ! hasLocale ) {
    System.err.println(
      ">>>>>>>> Date tag must have either " +
      "locale or localeCode attribute" );
    return false;
  }
  // otherwise, the tag is valid
    return true;
}

If we try to use the tag without setting locale or localeCode, for instance, we get the following message (in a server running Tomcat 4.1.29):

HTTP Status 500 -

type Exception report

message

description The server encountered an internal
error () that prevented it from fulfilling
this request.

exception

org.apache.jasper.JasperException:
/testToday.jsp(15,0) jsp.error.invalid.attributes
at org.apache.jasper.compiler.DefaultErrorHandler.
  jspError(DefaultErrorHandler.java:94)
at org.apache.jasper.compiler.ErrorDispatcher.
  dispatch(ErrorDispatcher.java:428)
    ...
    ...

This message means that the page was not compiled due to a jsp.error.invalid.attributes error. It is not a helpful message, though, as it does not inform us which attributes were invalid. This happens because the isValid() method only checks if the tag attributes are invalid, not why they are invalid. Fortunately, that situation has changed in JSP 2.0, which defines a method called ValidationMessage[] validate(TagData data). So our new method would be:

public ValidationMessage[] validate(
       TagData tagData ) {
 
  Enumeration enum = tagData.getAttributes();
  boolean hasLocale = false;
 
  while ( enum.hasMoreElements() ) {
    String attr = (String) enum.nextElement();
    Object value = tagData.getAttribute( attr );
    System.out.println(
      "attr(" + tagData.getId() + "): " +
       attr + " value: " + value );
    if ( attr.equals("locale") ||
         attr.equals("localeCode") ) {
      if ( hasLocale ) {
        return validationError(
          tagData.getId(),
          "Date tag has both locale and " +
          "localeCode attributes" );
      } else {
        hasLocale = true;
      } // if hasLocale
      } // if equals
  } // while
   if ( ! hasLocale ) {
    return validationError(
      tagData.getId(),
      "Date tag must have either locale " +
      "or localeCode attribute" );
  }
 
  return null;
}

And the output on a server running Tomcat 5.0.16:

HTTP Status 500 -

type Exception report

message

description The server encountered an internal
error () that prevented it from fulfilling
this request.

exception

org.apache.jasper.JasperException:
/testToday.jsp(16,0)
Validation error messages from TagExtraInfo
for helper:today

Date tag must have either locale or
localeCode attribute

Taglib Validation with TLV

Now let's go back to the pageGuard tag. This tag is crucial to our site security mechanism, and therefore it must be used in all JSP pages. So how can we assure the tag is included in all of our pages? One way would be to define a Tag Library Validator (TLV) for the mySite taglib.

If a TLV is defined for a taglib, every time a JSP page using that taglib is compiled, the JSP engine "asks" the TLV to validate the page, by calling its ValidationMessage[] validate(String prefix, String uri, PageData page) method. The parameters prefix and uri indicate how the taglib is mapped (i.e., how the taglib directive was used in the page) and page represents the XML view of the JSP being validated. If the page is not valid, that method should return a list describing what makes it invalid; otherwise it should return null or an empty list.

With these objects in hand, we can parse the XML view and do our validation, as shown below:

package net.felipeal.view.taglib;

import javax.servlet.jsp.tagext.*;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class MandatoryPageGuardTLV
       extends TagLibraryValidator {

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

    // create a handler that will parse the XML
    MyHandler handler = new MyHandler( prefix );

    // parse the page...
    try {
      SAXParserFactory factory =
        SAXParserFactory.newInstance();
      factory.setValidating( true );
      SAXParser parser = factory.newSAXParser();
      parser.parse( pageData.getInputStream(),
                    handler );

    } catch (Exception e) {
      getValidationMessages(
        e.getClass().getName() +
        " parsing document: " + e.getMessage() );
    }
    // ... and return the error (if any)
    return getValidationMessages(
            handler.getErrorMessage() );

  }

  // MyHandler is a XML parser that will check if
  // the pageGuard tag is present in the page
  private class MyHandler extends DefaultHandler {

    private String guardTag = null;
    private boolean foundGuard = false;
    private String errorMessage = null;

    public MyHandler(String prefix) {
      this.guardTag = prefix + ":pageGuard";
    }

    public void startElement(
           String uri, String localName,
           String qName, Attributes attributes )
      throws SAXException {

      // found the guard tag
      if ( qName.startsWith(this.guardTag) ) {
        this.foundGuard = true;
      } //if

    } // startElement

    String getErrorMessage() {
      return this.errorMessage;
    }

    public void endDocument() throws SAXException {
      if (!this.foundGuard) {
        this.errorMessage = this.guardTag +
                            " is mandatory";
      } // if
    } // endDocument

  } // private class

  // helper method
  public static ValidationMessage[]
         getValidationMessages( String msg ) {
    if ( msg == null  ) {
      return null;
    }
    ValidationMessage[] array =
      new ValidationMessage[] {
        new ValidationMessage( null, msg )
      };
    return array;
  }

} // public class

It is also necessary to change the TLD to include a validator:

<taglib>
  <tlib-version>1.0</tlib-version>
  <jsp-version>1.2</jsp-version>
  <short-name>mySite</short-name>
  <uri>http://felipeal.net/taglib/mySite</uri>
   
  <validator>
    <validator-class>
      article.tags.MandatoryPageGuardTLV
    </validator-class>
  </validator>

 
  <tag>
    <name>pageGuard</name>
    <-- remainder omitted as it did not change -->

Again, here is a JSP page that breaks the rule:

<%@ taglib prefix="mySite"
    uri="http://felipeal.net/taglib/mySite" %>

<%-- pageGuard tag should be here
<mySite:pageGuard role="admin"/>
--%>
HTML code goes here...

And its output in Tomcat 4.1.29:

HTTP Status 500 -

type Exception report

message

description The server encountered an internal
error () that prevented it from fulfilling
this request.

exception

org.apache.jasper.JasperException:
jsp.error.tlv.invalid.page

null: mySite:pageGuard is mandatory

at org.apache.jasper.compiler.DefaultErrorHandler.
  jspError(DefaultErrorHandler.java:105)
at org.apache.jasper.compiler.ErrorDispatcher.
  dispatch(ErrorDispatcher.java:430)
    ...
    ...

It is also possible to pass initialization parameters to the validator. If you take a closer look at the code above, you see that the guard tag's name is hardcoded to pageGuard. Using initialization parameters, we could eliminate this restriction, making our TLV a reusable component. The new TLD definition would looks like this:

<validator>
  <validator-class>
    article.tags.MandatoryPageTagTLV
  </validator-class>
  <init-param>
     <param-name>tagName</param-name>
      <param-value>myPageGuard</param-value>
  </init-param>

</validator>

And the new code:

public class MandatoryPageGuardTLV
       extends TagLibraryValidator {

  private String tagName = null;
  private static final String
    DEFAULT_TAGNAME = "pageGuard";

  public void setInitParameters(Map parameters) {
    this.tagName =
      (String) parameters.get("tagName");
    if ( this.tagName == null ) {
      this.tagName = DEFAULT_TAGNAME;
    }
  }

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

    // create the handler that will parse the XML
    MyHandler handler =
      new MyHandler( prefix + ":" + this.tagName );
    // remainder of method omitted as
    // it did not change

  }
  // remainder of class omitted as
  // it did not change


  private class MyHandler extends DefaultHandler {

    public MyHandler( String guardTag ) {
      this.guardTag = guardTag;
    }
  }
  // remainder of class omitted as
  // it did not change

Finally, it is important to mention that we can validate only the pages that use the taglib. If the page author does not use the taglib in a page, there is nothing we can do. This situation also changed in JSP 2.0, where you can set a prelude to a group of JSP pages. The prelude is automatically included at the beginning of every page in the group, and hence you can set the taglib directive there.

JSTL TLVs

The JSP Standard Tag Library (JSTL) (see the java.net article "Practical JSTL," parts 1 and 2, for more details) is well known for its taglibs and EL support. What most people do not know is that JSTL also provides two TLVs that can be reused in their own taglibs:

  • ScriptFreeTLV: Restricts the use of JSP elements, such as scriptlets and expressions.
  • PermittedTaglibsTLV: Restricts which taglibs (besides the one using the TLV) can be used in the page.

These TLVs are very useful to enforce page authors to write MVC-compliant pages, as they can restrict what can and cannot be used in a JSP 1.2 page. (In JSP 2.0, some of these restrictions can be set at the web-descriptor level, without the need for a TLV.)

ScriptFreeTLV takes four Boolean parameters: allowScriptlets, allowDeclarations, allowExpressions, and allowRTExpressions. If you want to forbid any JSP element (except JSP actions) in your pages, you must set all parameters to false, as shown below:

<taglib>
  <tlib-version>1.0</tlib-version>
  <jsp-version>1.2</jsp-version>
  <short-name>tlv1</short-name>
  <uri>http://felipeal.net/tags/jsp_free</uri>
  <validator>
    <validator-class>
javax.servlet.jsp.jstl.tlv.ScriptFreeTLV
    </validator-class>
    <init-param>
    <param-name>allowScriptlets</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
    <param-name>allowDeclarations</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
    <param-name>allowExpressions</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
    <param-name>allowRTExpressions</param-name>
        <param-value>false</param-value>
    </init-param>
  </validator>
  <tag>
    <name>Dummy tag</name>
    <tag-class>dev.null</tag-class>
  </tag>
</taglib>

Notice that it is necessary to define at least one tag element, even if it is a dummy one. The JSP page below disobeys the TLV rules:

<%@ taglib prefix="tlv1"
    uri="http://felipeal.net/tags/jsp_free" %>
<%@ taglib prefix="c_rt"
    uri="http://java.sun.com/jstl/core_rt" %>
Testing jsp_free

1 RT expression: <c_rt:out value="<%=request%>"/>
2 expressions: <%=%><%=%><br>
3 scriptlets: <%%><%%><%%><br>
4 declarations<%!%><%!%><%!%><%!%><br>

Consequently, it cannot be compiled, and its access results in an internal error:

500 Internal Server Error
Error: Validator
javax.servlet.jsp.jstl.tlv.ScriptFreeTLV reports:
JSP page contains 4 declarations, 3 scriptlets,
2 expressions, 1 request-time attribute value.

Similarly, PermittedTaglibsTLV takes as a parameter (permittedTaglibs) a list of space-delimited URIs representing the permitted taglibs in a page (besides the taglib using the TLV, of course), as shown in the TLD below:

<taglib>
  <tlib-version>1.0</tlib-version>
  <jsp-version>1.2</jsp-version>
  <short-name>tlv2</short-name>
  <uri>
    http://felipeal.net/tags/taglibs-restricted
  </uri>
  <validator>
    <validator-class>
    javax.servlet.jsp.jstl.tlv.PermittedTaglibsTLV
    </validator-class>
    <init-param>
      <param-name>permittedTaglibs</param-name>
      <param-value>
          http://java.sun.com/jstl/core_rt
          http://java.sun.com/jstl/core
      </param-value>
    </init-param>
  </validator>
  <tag>
    <name>dummy</name>
    <tag-class>dev.null</tag-class>
  </tag>
</taglib>

So if a JSP page tries to use a taglib other than core, core_rt, or tlv2 (which is the taglib using the TLV):

<%@ taglib prefix="tlv2"
  uri="http://felipeal.net/tags/taglibs-restricted"%>
<%@ taglib prefix="tlv1"
  uri="http://felipeal.net/tags/jsp_free"%>
Testing taglibs_restriction

it generates an error like this:

Error: Validator
javax.servlet.jsp.jstl.tlv.PermittedTaglibsTLV
reports:
taglib tlv2 (http://felipeal.net/tags/jsp_free)
allows only the following taglibs to be imported:
[http://java.sun.com/jstl/core_rt,
http://java.sun.com/jstl/core]

Conclusion

Taglib validation is a valuable tool to the taglib developer. Still, it is a technology that has not been widely adopted yet (as far as I know, for instance, JSTL is one of the few taglibs out there that uses TLVs). In my opinion, the slow adoption has two main causes:

  • Lack of acknowledgment that this technology even exists.
  • The technology is still complex.

With this article, I hope this situation is improved a little bit.

Felipe Leme has worked professionally with Java since 1996, and in the last years had became an active enthusiastic of the technology.
Related Topics >> JSP   |