Validating Custom Tags at Translation Time Blog

Version 2

    {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 aString object that represents the locale's language and country (like en_US or pt_BR).
    • By passing an existing java.util.Localeobject 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 calledmySite. 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 aNullPointerException.

    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.TagExtraInfoand override the method boolean isValid(TagDatadata), 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 returntrue 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 orlocaleCode, 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 ajsp.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, notwhy they are invalid. Fortunately, that situation has changed in JSP 2.0, which defines a method called ValidationMessage[]validate(TagDatadata). 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, PageDatapage) method. The parameters prefix anduri indicate how the taglib is mapped (i.e., how the taglib directive was used in the page) and pagerepresents 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.

      
    http://today.java.net/im/a.gif