9.5 Custom Component Development

I l @ ve RuBoard

In addition to the JSP elements, you can develop two types of custom components to use in your JSP pages: custom tag libraries and JavaBeans components .

9.5.1 Choosing Between Beans and Custom Actions

Beans and custom actions (packaged in a tag library) are regular Java classes that just happen to adhere to a few rules. This means they can do anything that any Java class can do. Whether it's best to implement application functionality as an action or a bean might not always be clear. My rule of thumb is that a bean is a great carrier of information, and a custom action is great for processing information.

A bean is a perfect vehicle for making information gathered or created in a servlet available to a JSP page. The standard <jsp:getProperty> action and the JSTL EL make it easy to access single-value bean properties, and the JSTL <c:forEach> action allows you to iterate through a multivalue property such as an array, a List , or a Map .

A bean can implement methods other than the property accessors to encapsulate functionality intended for use in many different environments, such as applets, servlets, and JSP pages. Calling such a method in a JSP page, however, requires scripting code and should be avoided. A custom action that internally uses the bean is a better choice. The custom action acts as a JSP-specific adapter for the bean to make it easier for a page author to use.

With the introduction of JSTL, the need for custom actions is significantly lower than it used to be, but occasionally, they are still needed. Examples include custom actions for conditional tests not covered by JSTL (e.g., testing if an authenticated user belongs to a specific role) and application-specific formatting (e.g.,, generating a complex table with various groupings and nested levels). Many custom actions of this type are available as open source or commercial offerings, so it's always a good idea to search the Web for solutions. A good place to start is Sun's JSP page: http://java.sun.com/products/jsp/. The examples from JavaServer Pages , Second Edition, available at http://TheJSPBook.com/, also include generic custom actions that you can use in your application.

9.5.2 Consider Using Immutable Objects

In an enterprise application, JSP pages are typically used only to render the response and should not be allowed to modify business objects maintained by other parts of the application. To ensure that a page author doesn't break this contract ”by mistake or intentionally ”you should consider passing only immutable objects to the JSP page.

An object is immutable if it doesn't expose direct access to its variables and doesn't provide methods that modify its internal state. Make all variables private and do not implement mutator methods ”i.e., methods used to change the property values. If the business logic needs to modify the object before passing it to the JSP page, make the mutator methods package-private or wrap the object in a read-only wrapper before you pass it to the JSP page, as shown here:

 public class MutableBean {     private String name;     private BigDecimal price;      . . .          public void setName(String name) {this.name = name;}     public String getName(  ) {return name;}     public void setPrice(BigDecimal price) {this.price = price;}     public BigDecimal getPrice(  ) {return price;}      . . .  }     public final class ReadOnlyWrapper {     private MutableBean bean;         public ReadOnlyWrapper(MutableBean bean) {this.bean = bean;}         public String getName(  ) {return bean.getName(  );}     public BigDecimal getPrice(  ) {return bean.getPrice(  );}      . . .  } 

If the object's variables hold references to mutable objects that must be exposed through public methods, return a copy of the object or an immutable representation instead:

 public final class ReadOnlyWrapper {     private MutableBean bean;         public ReadOnlyWrapper(MutableBean bean) {this.bean = bean;}         public String getName(  ) {return bean.getName(  );}     public BigDecimal getPrice(  ) {return bean.getPrice(  );}  public Date getPublicationDate(  ) {   return (Date) bean.getPublicationDate(  ).clone(  );   }   public List getAuthorNames(  ) {   return Collections.unmodifiableList(bean.getAuthorNames(  ));   }  . . .  } 

9.5.3 Consider Using a Reset Property When Capturing Input

In a small application, JSP pages can do more than generate responses. The JSP standard <jsp:setProperty> action, for instance, makes it easy to capture user input in a bean:

 <jsp:setProperty name="myBean" property="*" /> 

The <jsp:setProperty> action gets a list of all request parameter names and calls all setter methods for bean properties with matching names . The setter methods for properties that don't match a parameter name are not called, nor are setter methods for properties that match parameters with an empty string value.

For a bean in the page or request scope, this is not a problem, assuming the bean provides appropriate default values for the properties that are not set. If the bean instead is kept in the session scope, and the user is supposed to be able to update its values, you need to be careful. The effect of the setter method calling rules is that if the user deselects all checkboxes in a group or leaves a text field empty in the form that invokes the page with the <jsp:setProperty> action, the properties representing the checkbox group and the text field are left untouched, not cleared as would be expected.

A workaround for this problem is to add a setter method for a dummy property named reset :

 public class MyBean implements Serializable {      . . .      public void setReset(String dummy) {         intProperty = -1;         stringProperty = null;          . . .      } } 

All the setter method does is reset all properties to their default values. The setter method is used like this in the JSP page:

 <jsp:setProperty name="myBean" property="reset" value="any value"/> <jsp:setProperty name="myBean" property="*" /> 

The first <jsp:setProperty> action resets all properties, and the second sets all properties matching the request parameters.

9.5.4 Always Include the <uri> Element in the TLD

The <uri> element in a JSP Tag Library Descriptor (TLD) is an optional element, intended for defining a default URI for the tag library that an authoring tool can use when generating JSP elements:

 . . .  <taglib>   <tlib-version>1.0</tlib-version>   <jsp-version>1.2</jsp-version>   <short-name>c</short-name>  <uri>http://java.sun.com/jstl/core</uri>  <display-name>JSTL core</display-name>   <description>JSTL 1.0 core library</description>    . . . 

The auto-discovery feature introduced in JSP 1.2 relies on the presence of this otherwise optional element, however. During application startup, the JSP container scans all files in the application's WEB-INF directory (including the content of JAR files) to locate all TLD files. For each TLD it finds, it looks for a <uri> element and records an implicit mapping between the URI and the location of the TLD file. Therefore, all you need to do to use the library is specify this default URI in the taglib directive; the container figures out from its mappings where the corresponding TLD file is. This dramatically simplifies tag library deployment over the mechanisms available in JSP 1.1, so you should always specify a default URI in the TLD for tag libraries you develop.

9.5.5 Design Tag Handler Classes for Instance Pooling

A JSP container is allowed to reuse a tag handler instance to improve performance, as long as it adheres to the strict rules defined in the JSP specification. Briefly, the rules state that a tag handler instance can be used for more than one occurrence of the corresponding custom action element, in the same or different page, only if the same set of attributes is used for all occurrences. Before reusing the tag handler, the container must call the setter methods for attributes that differ between the occurrences (ensuring that the correct values are used for each occurrence), but it's not required to call the setter methods for attributes with the same value. You should therefore not do anything fancy in the setter methods; just save the value in an instance value and do all the real work in the doXXX( ) methods: doStartTag( ) , doAfterBody( ) , and doEndTag( ) .

While these rules guarantee consistent treatment of the setter methods, they say nothing about how to deal with variable values that are set through other means during the processing of the tag handler. If you believe the release( ) method is called between each reuse of the tag handler, giving you a chance to reset the internal state, you're wrong, but don't feel bad ”the JSP 1.1 specification was unclear about this, and this misunderstanding about the release( ) method is very common. There's also no guarantee that the doEndTag( ) method is called; in case of an uncaught exception while processing the body of the custom action element, or in the tag handler itself, the doEndTag( ) is not called.

The lifecycle rules are arguably complex and nonintuitive, but all you really need to be concerned about is how to deal with tag handler variables not set by the attribute setter methods:

  • A default value for an optional attribute can be set in the instance variable declaration, returned by a getter method that is used internally when the attribute has not been set, or handled as needed by the code in the doXXX( ) methods. The rules described earlier guarantee that a tag handler instance is reused only for custom action elements in which the identical set of attributes is used, so there's no need to reset variables to their default values.

  • An internal variable value that's valid during only one invocation of the tag handler should be reset in the doStartTag( ) method, or in the doFinally( ) method if the tag handler implements the TryCatchFinally interface. Typically, this is needed when a variable is set by tag handlers for nested action elements, such as nested parameter actions that add parameters to a list used by the parent in its doEndTag( ) method. Another example is when a variable set in doStartTag( ) is needed in the other doXXX( ) methods. Use the doStartTag( ) method (because doEndTag( ) might not be called, as described earlier) to reset the variable when it doesn't matter if the reset is delayed for some time; implement the TryCatchFinally interface and reset the variables in doFinally( ) only if it's critical that the value is reset immediately (for instance, if a value represents an expensive resource that must be closed).

  • An instance of a helper class that's expensive to create, such as a java.text.SimpleDateFormat object, can be created in the tag handler's constructor and saved in an instance variable until the tag handler is no longer reused to improve performance. The release( ) method gives you a chance to explicitly close such instances, if needed.

If you don't adhere to these rules ( especially the second one), your custom tag library will not work correctly in a container that reuses tag handler instances for multiple custom action elements.

9.5.6 Consider Adopting the JSTL Conventions

The JSTL specification defines a number of conventions you should consider following for your own custom tag library to make it easier for a page author familiar with JSTL to use your library. The following JSTL conventions are generic enough to apply to any tag library:

  • Expose data created by an action only through scoped variables (i.e., as attributes of the page context, request, session, or application), never as scripting variables. When a scripting element needs access to the data (which should be rare), the standard <jsp:useBean> action can be used to create a scripting variable and assign it a reference to the scoped variable.

  • Provide attributes named var and scope (unless the data has "nested visibility" ”see next rule) that the page author can use to define the name and scope for the exposed data. If more than one variable is exposed, use the var and scope attributes for the most commonly used variable and provide additional attributes starting with var and scope ”e.g., varDom and scopeDom ”for the others.

  • If the exposed data has "nested visibility" ”i.e., it's available only within the action element's body ”the scope attribute should not be provided. Instead, the variable should be placed in the page scope before the body is evaluated and removed at the end of the action processing.

  • All attributes except var and scope should accept a dynamic value (a Java or EL expression evaluated at runtime). This provides maximum flexibility, while making it possible to introduce type-checking features in a future JSP or JSTL specification.

  • Use the Java variable name capitalization rules for compound words in action and attribute names ”e.g., <xmp:ifUserInRole> and roleName .

  • Wherever possible, handle null attribute values gracefully ”e.g., by using a default value instead of throwing an exception. Given the nature of the web application environment, a null value is often just an indication of the absence of an optional request parameter, and forcing tests for null and assignments of default values using JSP elements just complicates the JSP page. An exception must, of course, be thrown when there's no way to handle a null value, as well as for other unrecoverable errors.

  • When a tag handler catches an exception that it can't handle, it should rethrow it as a JspException with the original exception assigned as the root cause. Most containers log the root cause exception, making it easier to find the real reason for the problem.

The JSTL libraries provide examples of other, less formal, conventions in addition to these, such as exposing a Reader as a variable with nested visibility to avoid double-buffering in some cases, which allows the main input to an action to be provided as either the action element's body or by an attribute, etc. I recommend that you familiarize yourself with the JSTL actions and mimic their syntax and semantics as much as possible when you design your own custom actions.

The most obvious JSTL convention is not listed in this section ”namely, allowing attribute values to be set as JSTL EL expressions. The reason for this omission is that the JSTL specification does not expose the EL evaluation API. You can, however, use the API in the Reference Implementation [3] (RI) to add support for EL expressions in your own tag library, but be aware that you must then bundle the RI (or require that it be installed separately). Another reason why it's hard to recommend supporting the EL in your own libraries as a best practice is that if you do so, you will likely want to remove the code for EL support as soon as support for the next version of the JSP specification is commonplace. In the next version of JSP (currently labeled 2.0), the JSP container will be responsible for evaluation of EL expressions so that no extra code is needed for this in the tag handlers.

[3] Available at http://jakarta.apache.org/ taglibs /doc/standard-doc/intro.html.

9.5.7 Leverage the JSTL Classes and Interfaces

The JSTL specification includes a number of classes and interfaces you can take advantage of when you develop your own custom tag libraries, including one class that can be used by a servlet or a filter to set configuration data used by the JSTL actions (such as the default locale and resource bundle for the formatting actions). It's beyond the scope of this book to show examples of how to use all these classes and interfaces, but here's a list of them so that you're at least aware of their existence and can study them in detail on your own:

javax.servlet.jsp.jstl.core.Config

This class provides methods for reading, setting, and removing configuration data used by the JSTL actions.

javax.servlet.jsp.jstl.core.ConditionalTagSupport

By extending this base class and providing an implementation of its abstract condition( ) method and your custom attribute setter methods, you get a tag handler that can be used exactly like the JSTL <c:if> action either to process its body when the condition is true or save the test result as a scoped variable.

javax.servlet.jsp.jstl.core.LoopTagStatus

An object implementing this interface can be obtained from a tag handler that implements the LoopTag interface, such as the JSTL <c:forEach> action. It provides a number of methods that tell you the current loop index, whether the first or last element is being processed , etc. This allows custom actions intended for use in the body of an iteration action to different things depending on the current iteration position.

javax.servlet.jsp.jstl.core.LoopTag

This interface provides access to a LoopTagStatus instance as well as the current iteration element. It's implemented by the <c:forEach> action, and if you implement it in your own iteration actions, nested actions that need to know about the iteration status will work with your custom action as well.

javax.servlet.jsp.jstl.core.LoopTagSupport

This is a base class you can extend to implement your own iteration actions. You need to implement three methods ( prepare( ) , hasNext( ) , and next( ) ), plus custom attribute setters, to get a fully functional iteration action with support for the same begin , end , and step attributes as the <c:forEach> action. And yes, you guessed it: this class implements the LoopTag interface, so nested actions that need to know the iteration status are supported as well.

javax.servlet.jsp.jstl.fmt.LocaleSupport

Tag handlers that need access to localized messages can use the static methods in this class. The algorithms for locating the appropriate resource bundle are the same as those for the JSTL i18n actions.

javax.servlet.jsp.jstl.fmt.LocalizationContext

An instance of this class provides information about a locale and resource bundle. A tag handler, or a servlet or a filter, can set a scoped variable (using the Config class) to an instance of this class to establish the defaults for the JSTL i18n actions.

javax.servlet.jsp.jstl.sql.SQLExecutionTag

This interface is implemented by the JSTL <sql:query> and <sql:update> actions. It provides one method that can be used by a nested custom action to set the value of a parameter placeholder in the SQL statement. You can use this in a custom action that merges information from a number of request parameters, or some other source, to create a single value for the database.

javax.servlet.jsp.jstl.sql.ResultSupport

Static methods in this class convert a JDBC ResultSet to a JSTL Result ”i.e., the type of object exposed by the <sql:query> action. It's a simple way for a custom action or servlet to create a cacheable version of a query result that might be handy in some applications.

Using these classes and interfaces will help you build tag libraries that follow the JSTL conventions and minimize your work at the same time.

9.5.8 Consider Developing a Tag Library Validator

To make the page author's life as easy as possible, you should consider implementing a tag library validator for your custom tag library. This type of validator was introduced in JSP 1.2, and it can do a much better job verifying that all custom actions are used correctly than the previous mechanism (the TagExtraInfo class) could.

You implement a tag library validator by extending javax.servlet.jsp. tagext .TagLibraryValidator and provide an implementation for the abstract validate( ) method:

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

The container calls this method when it translates the page into a servlet. The parameters provide the validator with the tag library prefix, URI (declared by the taglib directive in the page), and, most importantly, a PageData instance. Through the PageData instance, the validator gains access to an XML representation of the complete page (called an XML view ). With access to the full page in a format that's easy to parse, it can make sure the actions are properly nested and appear in the right order, in addition to what's always been possible: checking the proper use of all attributes for each individual custom action element.

The validate( ) method returns an array of ValidationMessage objects. Each object represents a validation error, containing a text message plus a unique element ID that the container can use to map the error message to the location of the corresponding element in the JSP source file. A JSP 1.2 container is not required to support the ID mechanism, but many do, so this greatly enhances the error-reporting capabilities compared to the old TagExtraInfo mechanism. In the next version of the JSP specification, support for ID mapping will be a mandatory container requirement.

I l @ ve RuBoard


The OReilly Java Authors - JavaT Enterprise Best Practices
The OReilly Java Authors - JavaT Enterprise Best Practices
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 96

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net