The Components of a Tag Library

printer-friendly version of this section  Print  e-mail this section  E-Mail  add a public, group or private note  Add Note  add a bookmark about this section  Add Bookmark    

JSTL: JSP Standard Tag Library Kick Start
By Jeff Heaton

Table of Contents
Chapter 11.  Creating Your Own Tag Libraries


The Components of a Tag Library

You have now seen the basic structure of the forum application's tag library. We must now implement this tag library and modify the JSP files to use the new tag library rather than the SQL tags we used in Chapter 10.

In essence, a tag library is a JAR and an XML-formatted tag library descriptor (TLD) configuration file. Installing a tag library consists of three steps:

  1. Copy the tag library descriptor file (*.tld) to the WEB-INF directory for your Web application.

  2. Modify the web.xml file for your Web application so that it includes the TLD file that you copied in step 1.

  3. Copy the tag library's JAR file to the /lib directory of your Web application so that your Web server can access it.

Let's now examine each of these steps in detail. In particular, we focus on the structure of the JAR and TLD files so that you will be able to create your own tag libraries that you can use to supplement JSTL with your own company-specific procedures.

The Tag Library Descriptor File (TLD)

The forum application's tag library stores its TLD information in a file called forum.tld. This file is quite lengthy, and will not be included here in its entirety. It contains definitions for each of the 16 tags that make up the forum tag library.

The TLD File Header

The forum.tld file begins with basic header information:

<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib>   <tlib-version>1.0</tlib-version>   <jsp-version>1.2</jsp-version>   <short-name>forum</short-name>   <uri>http://java.jeffheaton.com/taglib/jstl/forum</uri>   <display-name>Forum Example</display-name>   <description>An example tag library used to implement a   forum.</description> 

This header contains basic information about the forum tag library and what sort of environment it expects. This information should be made available for any TLD file that you create. One of the most important elements is the <uri> tag. This identifier enables the JSP pages to use this tag library. As you may recall from Chapter 1, "Understanding JSP Custom Tags," we used the following taglib directive:

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> 

As you can see, a URI is specified. This URI corresponds to a URI specified in the TLD for the core JSTL tag library. If you wanted to use the forum tag library, you must use a similar taglib directive, except you insert the forum URI:

<%@ taglib uri="http://java.jeffheaton.com/taglib/jstl/forum"   prefix="forum" %> 

The URI is not an actual Web address. If you copy and paste a URI from a tag library into a browser, you may or may not see anything of meaning. The URI is simply a common identifier that is used to tie the JSP pages to the appropriate TLD file. Next, we look at the tag definitions that follow the header.

Tag Definitions

After the header information, you see <tag> tags that specify the individual tags in the library. This tag is defined in our forum application tag library:

<tag>   <name>loadUser</name>   <tag-class>com.heaton.forum.LoadUserTag</tag-class>   <body-content>empty</body-content>   <description>Used to load a user.</description>   <attribute>     <name>var</name>     <required>true</required>     <rtexprvalue>false</rtexprvalue>   </attribute>   <attribute>     <name>id</name>     <required>true</required>     <rtexprvalue>false</rtexprvalue>   </attribute>   <attribute>     <name>scope</name>     <required>false</required>     <rtexprvalue>false</rtexprvalue>   </attribute> </tag> 

This tag defines the <forum:loadUser> tag. Each tag consists of two parts. First, each tag has header information of its own. This includes the tag's name and other important information. Following the tag's header information are several attribute tags. If no attributes are used with the tag, then this section is empty.

Table 11.2 describes the tag header elements.

Table 11.2. Tag Header Elements

Element Name

Purpose

attribute

There will be zero or more such elements. They specify the attributes that can be used with this tag. Table 11.3 defines the elements that make up an attribute node.

body-content

Specifies the type of body that this tag has. If the tag has no body, the value empty should be used. If other JSP code can be contained in the body, the value jsp should be used.

description

A text description that defines what this tag does.

name

The name of the tag. This is the name the JSP code will use when referring to this tag.

tag-class

The class that implements the tag.

The attribute element contains several subelements. This allows you to include information about each of the attributes. Table 11.3 summarizes these subelements.

Table 11.3. Tag Attribute Subelements

Element Name

Purpose

name

The name of the attribute.

required

A true/false value that specifies whether this tag is required. If the tag is specified as required and the tag is not provided, an error will be raised before your tag is executed.

rtexprvalue

Allows RT expressions to be used for the value of the attribute. RT expressions are expressions such as <%=request.getMethod()%>.

You'll notice that the tags do not accept RT expressions. Consider the following tag:

<testlib:printIt value="<%=10*10%>"/> 

If you had specified a value of false for the rtexprvalue attribute, your tag would get the value <%=10*10%> when the value attribute was retrieved. If a value of true had been specified for the rtexprvalue attribute, the value 100 would be retrieved. The tags we are using in this example are designed to use EL expressions and not RT expressions.

Whenever possible, you should use EL expressions rather than RT expressions. EL expressions are an important part of JSP 1.3 and offer more flexibility than the older RT expressions. Because of this you will usually set the rtexprvalue attribute to false. The forum tag library tags do not make use of RT expressions. The tag that we just examined could be expressed using an EL expression as follows:

<testlib:printIt value="${10*10}"/> 

Setting the rtexprvalue attribute to true will not help you process EL expressions. Processing EL expressions is important if you want your tag library to be used in conjunction with the JSTL tag libraries. We discuss processing EL expressions later in this chapter.

Updating web.xml for Custom Tags

Now that you have created a TLD file for your tag library, you must modify your web.xml file so that it uses the TLD file. Listing 11.4 shows the web.xml file for our forum application.

Listing 11.4 The web.xml File
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>   <context-param>     <param-name>javax.servlet.jsp.jstl.fmt.timeZone</param-name>     <param-value>US/Central</param-value>   </context-param>   <context-param>     <param-name>database-driver</param-name>     <param-value>org.gjt.mm.mysql.Driver</param-value>   </context-param>   <context-param>     <param-name>database-url</param-name>     <param-value>     jdbc:mysql://localhost/forum?user=forumuser</param-value>   </context-param>   <taglib>     <taglib-uri>http://java.sun.com/jstl/fmt</taglib-uri>     <taglib-location>/WEB-INF/fmt.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/fmt-rt</taglib-uri>     <taglib-location>/WEB-INF/fmt-rt.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/core</taglib-uri>     <taglib-location>/WEB-INF/c.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/core-rt</taglib-uri>     <taglib-location>/WEB-INF/c-rt.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/sql</taglib-uri>     <taglib-location>/WEB-INF/sql.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/sql-rt</taglib-uri>     <taglib-location>/WEB-INF/sql-rt.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/x</taglib-uri>     <taglib-location>/WEB-INF/x.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>http://java.sun.com/jstl/x-rt</taglib-uri>     <taglib-location>/WEB-INF/x-rt.tld</taglib-location>   </taglib>   <taglib>     <taglib-uri>     http://java.jeffheaton.com/taglib/jstl/forum</taglib-uri>     <taglib-location>/WEB-INF/forum.tld</taglib-location>   </taglib> </web-app> 

The file in Listing 11.4 contains several important pieces of information. The <taglib> elements are used to specify configuration information to be used with the forum tag library. Let's look at the <taglib> statements next.

Including Tag Libraries in web.xml

Each of the <taglib> elements that you see in Listing 11.4 is a tag library that we are making available. Most of the <taglib> elements are used to include JSTL tag libraries. This element is used to include the forum application's tag library:

<taglib>   <taglib-uri>   http://java.jeffheaton.com/taglib/jstl/forum</taglib-uri>   <taglib-location>/WEB-INF/forum.tld</taglib-location> </taglib> 

As you can see, we've provided a path to the TLD file. The URI is also specified again with the <taglib-uri> element. By including this taglib node in your web.xml file, you make the tag library available.

Of course, taglib directives are not the only thing stored in the web.xml file. You can also store configuration information about your application.

Storing Configuration Information in web.xml

The forum example in Chapter 7 contained two index.jsp files. We configured the main index.jsp file to work with the MySQL database, and we configured the secondary index-msa.jsp file to work with Microsoft Access. This is no longer necessary now that we are using a custom tag library. In the version of the forum example presented in this chapter, we store information about the database connection in the web.xml file, and allow our tag library and JSP files to remain free of ties to Microsoft Access or MySQL.

Configuration information stored in the web.xml file is contained in <context-param> tags. The following two <context-param> tags are used to store information about the database we'll use:

<context-param>   <param-name>database-driver</param-name>   <param-value>org.gjt.mm.mysql.Driver</param-value> </context-param> <context-param>   <param-name>database-url</param-name>   <param-value>   jdbc:mysql://localhost/forum?user=forumuser</param-value> </context-param> 

A context tag named database-url provides the JDBC URL of the database we plan to access. A second context tag, named database-driver, is used to specify the database driver that we'll use to connect to the database. This code is configured to work with a MySQL database. If you want the example to work with the Microsoft Access database provided in Chapter 7, you have to modify the code as follows:

<context-param>   <param-name>database-driver</param-name>   <param-value>sun.jdbc.odbc.JdbcOdbcDriver</param-value> </context-param> <context-param>   <param-name>database-url</param-name>   <param-value>   jdbc:odbc:forum</param-value> </context-param> 

This code assumes that you are using the DSN forum as specified in Chapter 7. The values stored in <context-param>, which is located in web.xml, can easily be accessed from a tag library. We see how this is done in the next section.

Creating the Tag Classes

So far, we've explained how to set up the configuration files that go with the tag libraries, but we haven't yet seen how to create the tag libraries. If you examine the files that make up the forum tag library, you'll notice that there are essentially three classes of files:

  • Tag files of the form xxxTag.java, such as LoadUserTag.java

  • Data classes such as Forum.java or User.java

  • The session management file, Session.java

We have already described the data classes. Next, we examine session management and the tag files.

Handling Sessions

Much of the functionality for our forum application's tag library is contained in the Session class. The Session class is used to ensure that each user session uses only one database connection. It would be inefficient for each user session to create and dispose of database connections for each SQL command issued.

One potential hazard of this approach is that database connections will remain open even after the user has closed the browser. Once the browser is closed, it will be 30 minutes (or whatever session timeout is set to) until the session is closed. In a very high-volume Web site, this could lead to high resource usage.

The best way to handle such an issue is to use a connection pool. Implementing a JDBC connection pool is beyond the scope of this book. You may want to look at a prebuilt JDBC connection pool, such as struts GenericDriver.

Note that the Session class is not a required component of a tag library. It is simply a utility class that we created to encapsulate some of the common functionality required by all of the forum application's tags. Because every tag in the forum example makes use of the Session class, let's begin by examining the Session class.

The Session class works by creating a new Session object for each user session. This is done by storing the Session object as a session-scoped attribute.

Obtaining a New Session

To obtain a Session object, the static method getSession() should be called from the Session object:

/**  * Factory method to create sessions. This method  * will ensure that there is only one object created  * per user session.  *  * @param ctx The page context.  * @return A Session object.  * @exception JspException Thrown on general error.  */   static Session getSession(PageContext ctx)   throws JspException   {       Session session = (Session)ctx.getAttribute(FORUM,PageContext.   SESSION_SCOPE);       if ( session==null ) {         String driver = ctx.getServletContext().   getInitParameter("database-driver");         String url = ctx.getServletContext().   getInitParameter("database-url");         session = new Session(driver,url);         ctx.setAttribute(FORUM,session,PageContext.SESSION_SCOPE);       }       return session;   } 

First, this method checks to see whether a Session object has already been created for this session:

    Session session = (Session)ctx.getAttribute(FORUM,PageContext. SESSION_SCOPE); 

If a session does not exist, then one is created:

if ( session==null ) { 

To open a connection to the database, the program must retrieve the database driver and URL. These variables will be used later to open connections to the database as needed. As we mentioned in the previous section, these two values are stored in the web.xml file. The following code retrieves these values:

      String driver = ctx.getServletContext(). getInitParameter("database-driver");       String url = ctx.getServletContext(). getInitParameter("database-url"); 

These two variables are passed to the constructor of the Session class:

session = new Session(driver,url); ctx.setAttribute(FORUM,session,PageContext.SESSION_SCOPE); 

The constructor to the Session class is simple. The database driver and URL are passed in and stored. The constructor for the Session class is shown here:

/**  * The constructor. Constructs a Session object  * from a page context. To create a session  * object you should use the getSession factory  * method.  *  * @param driver A database driver  * @param driver A database url  */   Session(String driver,String url) {   this.url = url;   this.driver = driver; } 

As the user uses the forum site, database connections are opened and closed as needed. The following method is used to open a connection to the database. This method makes use of the database driver and URL obtained previously:

/**  * Factory method to create sessions. This method  * will ensure that there is only one object created  * per user session.  *  * @return A Connection object.  * @exception JspException Thrown on general error.  */   Connection getConnection()   throws JspException   {     try {         Class.forName(driver);         Connection conn = DriverManager.getConnection(url);         return conn;     } catch ( ClassNotFoundException e ) {       throw new JspException(e);     } catch ( SQLException e ) {       throw new JspException(e);   } } 

Our program can now connect to the database. This is accomplished with the usual JDBC commands for establishing a database connection:

Class.forName(driver); conn = DriverManager.getConnection(url); 

Now that the database connection has been opened, we can instantiate a new Session object. The object receives the connection to the database and is registered as a session-scoped attribute.

Handling Scope

The forum tag library must frequently create scoped variables to contain the data that the tag library retrieves. Unfortunately, when the scoped variables are created, their scope must be specified using integer constants stored in the PageContext class:

/**  * Called to get the int constant for a character scope  * value such as "page.  *  * @param str A string that should contain "page", "session", etc.  * @return The numeric form of the specified scope.  */   static public int getScope(String str)   {     if ( str==null )       return PageContext.PAGE_SCOPE;     if ( str.equalsIgnoreCase("request") )       return PageContext.REQUEST_SCOPE;     else if ( str.equalsIgnoreCase("session") )       return PageContext.SESSION_SCOPE;     else if ( str.equalsIgnoreCase("application") )       return PageContext.APPLICATION_SCOPE;     else return PageContext.PAGE_SCOPE;   } 

As you can see, the getScope() method is simple. The input string is compared against four constant string values that represent each of the four possible scopes. Once the scope is determined, the integer constant for that scope is returned. This method is called any time a scope attribute is specified in any of the forum application tags. This will allow the variable access to occur at the correct scope.

Retrieving a Single Record

One of the primary jobs of the Session class is to perform database queries. All of the JDBC code used by the forum application is concentrated here. The Session class includes various methods for database access. We won't cover each of them in this chapter because they all operate in a similar manner. Instead, we highlight some of database access methods that are representative of how the others work.

One such method is getUser(). This method performs one of the most basic operations of the Session class: It retrieves a single record. The getUser() method is shown here:

/**  * Called to load a user from the database.  *  * @param id The user that is to be loaded.  * @return A user object.  * @exception JspException Thrown on general error.  */   public User loadUser(String id)   throws JspException   {     Connection connection = null;     PreparedStatement stmt = null;     try {       connection = getConnection();       stmt = connection.prepareStatement                                ("select c_uid,c_pwd,c_accesses,c_   first,c_last,c_bad,c_posted,c_type " +                                 "from t_users " +                                 "where c_uid=?");       stmt.setString(1, id.toLowerCase());       ResultSet rs = stmt.executeQuery();       if ( !rs.next() )         return null;       User user = new User();       user.setId(rs.getString("c_uid"));       user.setPassword(rs.getString("c_pwd"));       user.setAccesses(rs.getInt("c_accesses"));       user.setFirst(rs.getDate("c_first"));       user.setLast(rs.getDate("c_last"));       user.setBad(rs.getInt("c_bad"));       user.setPosted(rs.getInt("c_posted"));       user.setType(rs.getString("c_type"));       return user;     } catch ( SQLException e ) {       throw new JspException(e);     }     finally     {         try         {         if(stmt!=null)             stmt.close();         if(connection!=null)             connection.close();         }         catch ( SQLException e ) {           throw new JspException(e);         }     }   } 

The getUser() method begins by preparing a SQL statement that will search for a user by that user's user ID. The SQL statement is constructed so that all of the fields associated with that user will be returned in the result set.

The individual values contained in the result set must be copied to the properties of a User object. This is done by a series of calls to the various put methods of the User object.

Not all accesses to the database are as simple as requesting a single record. Often, multiple records will have to be returned, or some update or insertion will need to be preformed against the database. Next, we discuss the process used to retrieve a collection of rows from the database. This process is nearly the same as the single-record access method, except that the collection method will need to process records in a loop.

Retrieving a Collection

Often, the data we want to retrieve is more than a single record. Sometimes, the forum application displays a table of data. To display such a table, the program must return a collection of data.

One such table is the list of all of the forum users. This list is accessible from the forum application's administration screens, and allows you to edit or delete users. To display this collection of users, the Session class provides a method named listUsers():

/**  * Called to return a collection of all  * of the users.  *  * @return A collection that contains all of  * the users.  * @exception JspException Thrown on general error.  */   public Collection listUsers()   throws JspException   {     Connection connection = null;     PreparedStatement stmt = null;     try {       connection = getConnection();       Collection c = new LinkedList();       stmt = connection.prepareStatement                                ("select c_uid,c_pwd,c_accesses,c_   first,c_last,c_bad,c_posted,c_type " +                                 "from t_users order by c_uid" );       ResultSet rs = stmt.executeQuery();       while ( rs.next() ) {         User user = new User();         user.setId(rs.getString("c_uid"));         user.setPassword(rs.getString("c_pwd"));         user.setAccesses(rs.getInt("c_accesses"));         user.setFirst(rs.getDate("c_first"));         user.setLast(rs.getDate("c_last"));         user.setBad(rs.getInt("c_bad"));         user.setPosted(rs.getInt("c_posted"));         user.setType(rs.getString("c_type"));         c.add(user);       }       return c;     } catch ( SQLException e ) {       throw new JspException(e);     }     finally     {         try         {         if(stmt!=null)             stmt.close();         if(connection!=null)             connection.close();         }         catch ( SQLException e ) {           throw new JspException(e);         }     }   } 

This method first instantiates a new ArrayList with an arbitrary length of 3. This collection will hold the individual User objects that will be created. The method begins by submitting a query to the database. This query retrieves all of the users currently registered and orders them by their user ID.

The program then loops through the returned records. It copies each field into the corresponding property of the User class. Once the query has been completely processed, the collection of User objects is returned to the calling method.

Encapsulating Functionality

In addition to retrieving individual and collections of records, another important benefit of moving the database functionality away from the JSP pages is that complex operations can be hidden from the presentation logic contained in the JSP pages. The two examples that we examined so far were simple result sets that were returned in essentially the same form as they were retrieved from the database. Database operations are not always that simple.

One such example from the forum application is what transpires when the user posts a message to one of the forums. When a message is posted to a forum, a new row must be added to the t_messages table. To properly add a message, the program must calculate a new message number. In addition, it must fill in the current date that the message was posted. These tasks are handled by the createMessage() method provided by the Session class:

/**  * Called to create a new forum message.  *  * @param code The forum that the message is to be created in.  * @param from The login id of the user that this message is from.  * @param subject The subject of this message.  * @param message The actual message text.  * @exception JspException Thrown on general error.  */   public void createMessage(String code,String from,String   subject,String message)   throws JspException   {     Connection connection = null;     PreparedStatement stmt = null;     try {       connection = getConnection();       code = code.toUpperCase();       stmt = connection.prepareStatement                                ("select max(c_number)+1 as msgnum   from t_messages"+                                 " where c_forum_code = ?");       stmt.setString(1, code.toUpperCase());       ResultSet rs = stmt.executeQuery();       int num;       if ( rs.next() )         num = rs.getInt("msgnum");       else         num = 1;       stmt = connection.prepareStatement              ("insert into t_messages(c_forum_code,c_number,c_   posted,c_subject,c_sender,c_message)"+               "values(?,?,now(),?,?,?)");       stmt.setString(1,code);       stmt.setInt(2,num);       stmt.setString(3,subject);       stmt.setString(4,from);       stmt.setString(5,message);       stmt.execute();       User user = loadUser(from);       user.setPosted(user.getPosted()+1);       saveUser(user);     } catch ( SQLException e ) {       throw new JspException(e);     }     finally     {         try         {         if(stmt!=null)             stmt.close();         if(connection!=null)             connection.close();         }         catch ( SQLException e ) {           throw new JspException(e);         }     }   } 

The createMessage() method must accomplish two key tasks to post the message. First, it must determine the message number that will be assigned to the newly created message. This is done by doing a SQL SELECT with the max aggregate function. Whatever the maximum message number used by the forum, the new message number will be one higher. Once this number is determined, the message can be posted.

The same Connection object can be reused with the second task. However, a new Statement object is instantiated. The message is inserted into the t_messages table using the SQL INSERT INTO command.

The message number, along with the parameter information, is copied to the statement for use by the insert statement. Once the Statement object is prepared, its execute() method is called and the new record is created.

Constructing the Tags

In the previous section, we saw how the database queries were constructed to perform three basic types of operations. In this section, we explain how these database operations are tied to the actual tags.

Retrieving a Single Record

First let's construct a tag that will return one record. We've named this tag <forum:loadUser>, and it will be responsible for loading an individual user from the database. Listing 11.5 shows the LoadUserTag class file that implements the custom tag.

Listing 11.5 Loading a User (LoadUserTag.java)
package com.heaton.forum; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import java.util.*; import java.io.*; import org.apache.taglibs.standard.tag.el.core.*; import java.sql.*; /**  * Example program from Chapter 11  * JSTL: JSP Standard Template Library Kick Start  * Published by Sams Publishing  *  *  * This file implements the loadUserTag tag for the  * forum custom tag library example.  *  * @author Jeff Heaton(http://www.jeffheaton.com)  * @version 1.0  */ public class LoadUserTag extends TagSupport {   private String id_el;,   private String var;   private String scope;   private String id;   public void setId(String id) {     this.id_el = id;   }   public void setVar(String var) {     this.var = var;   }   public void setScope(String scope) {     this.scope = scope;   }   /**    * The start of the tag, used to process the EL    * expressions.    *    * @return A status to indicate if the body is to be    * processed. Here the body is not processed.    * @exception javax.servlet.jsp.JspException Thrown on general   error.    */   public int doStartTag()   throws  javax.servlet.jsp.JspException   {     id = (String)ExpressionUtil.evalNotNull(       "setUser", "id", id_el, String.class, this, pageContext);     return SKIP_BODY;   }   /**    * The ending tag method is called when the tag    * is ready to complete. It is here that    * the user is loaded.    *    * @return A status code that indicates if the rest of the    * page is to be processed.    * @exception JspException Thrown on general error.    */   public int doEndTag() throws JspException {     Session session = Session.getSession(pageContext);     User user = session.loadUser(id);     if ( user==null ) {       pageContext.removeAttribute(var);     } else {       pageContext.setAttribute(var,user);     }     return EVAL_PAGE;   } } 

You'll notice that several methods and properties are used to implement the loadUser tag. Let's examine these components so you can easily create your own tag libraries.

Accessing Attributes

One of the most basic components of any JSP tag is the attributes given to that tag. The <forum:loadUser> tag must be provided with the user ID to be loaded. Likewise, the <forum:loadUser> tag must be provided with the name of the scoped variable in which the user is to be stored. Finally, the <forum:loadUser> tag must be provided with the scope that the scoped variable must be created in.

This gives the <forum:loadUser> three attributes, named id, var, and scope. Calling <forum:loadUser> to load the admin user might look like this:

<forum:loadUser  var="user" scope="page"/> 

Your program must be able to read in each of these values if the tag is to function properly. Reading in these three values would be quite easy, since they are just simple constant values. There is no command that actually reads in these values. To gain access to these values, you must create set methods for each of them. The Web server calls your set methods and allows your tag to receive all three attributes. For example, we use the setId() method to retrieve the value of the id attribute:

public void setId(String id) {   this.id_el = id; } 

This set tag stores the value specified for the id attribute in the variable id_el. If your tag is provided only to support simple constant values, this is sufficient.

Evaluating EL Expressions

If you would like your tag to be able to accept EL expressions, you must take a few additional steps. Consider the following tag, which uses an EL expression:

<forum:loadUser  var="user" scope="page"/> 

In this tag, the user ID is not just a simple constant value. When our set method is called, it is passed the string ${param.uid}. We want this value to be evaluated, but it is not evaluated in the set method. Instead, the set method simply stores it away in the id_el variable for the doStartTag() method to evaluate. The only operation performed by the doStartTag() method of the <forum:loadUser> tag is to evaluate the value of the id attribute:

id = (String)ExpressionUtil.evalNotNull(   "setUser", "id", id_el, String.class, this, pageContext); 

This code allows you to call the Apache expression evaluator to evaluate the EL expression.

The ExpressionUtil.evalNotNull() method is provided by the Apache implementation of JSTL and allows you to access individual attributes of your tags. Its primary benefit is that it is capable of evaluating EL expressions.

WARNING

The ExpressionUtil class is not a part of the standard JSTL implementation. This is a class that the Apache implementation of JSTL included for convenience. The command to evaluate an EL expression when using an implementation of JSTL other than Apache may be slightly different. It is also important that the JAR files servlet.jar, standard.jar, and jstl.jar all be added to the classpath so that your tag has access to the ExpressionUtil class while your tag is being compiled. This is covered in greater detail in Appendix B, "Installing Tomcat and JSTL."


Once the result of the expression has been stored in the id variable, the work of the doStartTag() method is done. The method can now return with the following statement:

return SKIP_BODY; 

Because the <forum:loadUser> tag does not expect body content, we can safely return the SKIP_BODY identifier to halt further evaluation of the tag's body. After the body is processed, the doEndTag() method is called.

The doEndTag() Method

Most bodyless tags perform their primary action in the doEndTag() method. In the case of the <forum:loadUser> tag, the doEndTag() method begins by obtaining the Session object so that a database command can be invoked:

Session session = Session.getSession(pageContext); 

Once we have access to the session, we can load the user. This is done by calling the loadUser() method of the Session object:

User user = session.loadUser(id); 

We discussed the loadUser() method earlier in this chapter. If the user is found, the loadUser() method returns null. If the value returned is null, then we want to remove the scoped variable, specified by the var attribute, from the context. This is done with the following code:

if ( user==null ) {   pageContext.removeAttribute(var); } else { 

If a valid user is returned, then we store this user in the scoped variable specified by the var attribute. The following code sets the appropriate scoped variable with the User object:

  pageContext.setAttribute(var,user); } 

Once the User object has been copied, the tag's work is nearly done. Now, the tag must return with a constant that specifies the next action to be taken:

return EVAL_PAGE; 

In this example, we use the constant EVAL_PAGE, which allows the rest of the page to be evaluated. If the SKIP_PAGE constant were returned, the remaining data on the page would not be evaluated.

Retrieving a Collection

Collections are an important part of the JSTL tag library. Many tags are provided that allow you to work with collections. This includes not only accessing the data members of the collections, but also iterating through them. By having your tag return a collection, you are able to use the <c:forEach> tag to process the elements of your collection.

One such example of this is the <forum:listUsers> tag. This tag returns a complete list of the users as a collection. This allows admin screens to display a complete listing of users for editing. The <forum:listUsers> tag is implemented inside ListUsersTag.java. The source code to ListUsersTag.java is shown in Listing 11.6.

Listing 11.6 The listUsers Tag (ListUsersTag.java)
package com.heaton.forum; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import java.util.*; import java.io.*; import org.apache.taglibs.standard.tag.el.core.*; import java.sql.*; /**  * Example program from Chapter 11  * JSTL: JSP Standard Template Library  * Published by Sams Publishing  *  *  * This file implements the listUsers tag for the  * forum custom tag library example.  *  * @author Jeff Heaton(http://www.jeffheaton.com)  * @version 1.0  */ public class ListUsersTag extends TagSupport {   private String var;   private String scope;   public void setVar(String var) {     this.var = var;   }   public void setScope(String scope) {     this.scope = scope;   }   /**    * The start of the tag, used to process the EL    * expressions.    *    * @return A status to indicate if the body is to be    * processed. Here the body is not processed.    * @exception javax.servlet.jsp.JspException Thrown on general   error.    */   public int doStartTag()   throws  javax.servlet.jsp.JspException   {     return SKIP_BODY;   }   /**    * The ending tag method is called when the tag    * is ready to complete. It is here that    * the users are listed.    *    * @return A status code that indicates if the rest of the    * page is to be processed.    * @exception JspException Thrown on general error.    */   public int doEndTag() throws JspException {     Session session = Session.getSession(pageContext);     Collection list = session.listUsers();     pageContext.setAttribute(var,list,Session.getScope(scope));     return EVAL_PAGE;   } } 

The <forum:listUsers> tag is similar in structure to the <forum:loadUser> tag. The primary difference is that the tag returns a collection rather than an individual user record.

Encapsulating Functionality

So far, we have seen tags that simply map to database queries. While a program will usually have many such tags, another key feature of tag libraries is their ability to encapsulate common functionality. PostMessageTag.java is shown in Listing 11.7.

Listing 11.7 Posting a Message (PostMessageTag.java)
package com.heaton.forum; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import java.util.*; import java.io.*; import org.apache.taglibs.standard.tag.el.core.*; import java.sql.*; /**  * Example program from Chapter 11  * JSTL: JSP Standard Template Library  * Published by Sams Publishing  *  *  * This file implements the postMessages tag for the  * forum custom tag library example.  *  * @author Jeff Heaton(http://www.jeffheaton.com)  * @version 1.0  */ public class PostMessageTag extends TagSupport {   private String code_el;   private String code;   private String from_el;   private String from;   private String subject_el;   private String subject;   private String message_el;   private String message;   public void setCode(String code) {     this.code_el = code;   }   public void setFrom(String from) {     this.from_el = from;   }   public void setSubject(String subject) {     this.subject_el = subject;   }   public void setMessage(String message) {     this.message_el = message;   }   /**    * The start of the tag, used to process the EL    * expressions.    *    * @return A status to indicate if the body is to be    * processed. Here the body is not processed.    * @exception javax.servlet.jsp.JspException Thrown on general   error.    */   public int doStartTag()   throws  javax.servlet.jsp.JspException   {     code = (String)ExpressionUtil.evalNotNull(       "postMessage", "code", code_el, String.class, this,   pageContext);     from = (String)ExpressionUtil.evalNotNull(       "postMessage", "from", from_el, String.class, this,   pageContext);     subject = (String)ExpressionUtil.evalNotNull(       "postMessage", "subject", subject_el, String.class, this,   pageContext);     message = (String)ExpressionUtil.evalNotNull(       "postMessage", "message", message_el, String.class, this,   pageContext);     return SKIP_BODY;   }   /**    * The ending tag method is called when the tag    * is ready to complete. It is here that    * the message is posted.    *    * @return A status code that indicates if the rest of the    * page is to be processed.    * @exception JspException Thrown on general error.    */   public int doEndTag() throws JspException {     Session session = Session.getSession(pageContext);     session.createMessage(code,from,subject,message);     return EVAL_PAGE;   } } 

From the tag's perspective, this is an easy operation. All of the work is done by the createMessage() method of the Session object. We defined this method earlier in this chapter.

The doEndTag() method begins by establishing a Session object. With access to the session, the method calls the createMessage() method. Nothing is returned by this method; the tag simply returns with the directive to process the rest of the page.

Tags with Bodies

Sometimes, you will want to create a custom tag that also has a body. One example of this is the <forum:expire> tag. This tag works like an if statement. If the user's session has expired, the program executes the body of the <forum:expire> tag. This tag is used in instances such as the following:

<forum:expire>   <c:redirect url="index.jsp" /> </forum:expire> 

This tag checks to see whether the user's session has expired. If it has, then the program executes the body of the tag. The body contains a single JSTL command that redirects the page back to the index.jsp file.

Let's now look at how to construct a tag that also contains a body. The source code behind the <forum:expire> tag is shown in Listing 11.8.

Listing 11.8 Checking to See if the User Has Expired (ExpireTag.java)
package com.heaton.forum; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.jstl.core.ConditionalTagSupport; /**  * Example program from Chapter 11  * JSTL: JSP Standard Template Library Kick Start  * Published by Sams Publishing  *  *  * This file implements the expireTag tag for the  * forum custom tag library example. This tag is  * used to determine if the session has timed out.  *  * @author Jeff Heaton(http://www.jeffheaton.com)  * @version 1.0  */ public class ExpireTag extends ConditionalTagSupport {   /**    * @return true if the Session object has not been created in the   session scope.    */   protected boolean condition() throws JspTagException {     return pageContext.getAttribute(Session.FORUM,PageContext.   SESSION_SCOPE) == null;   } } 

With a tag such as <forum:expire>, the majority of processing is done in the condition() method rather than the doEndTag() method. This is because we overrode the ConditionalTagSupport class. The ConditionalTagSupport class is used to provide tags similar to <c:if>. The method simply returns true if the body is to be executed or false if it is not.

There are other helper classes like ConditionalTagSupport. You are not required to use these helper classes. For example, we could have implemented the expire tag using the TagSupport class. Other helper classes provided by JSTL include:

  • LoopTagStatus Provides status information for loops

  • LoopTagSupport Provides tags that repeat their body statements in a loop

  • LocaleSupport Used for I18N support

  • SQLExecutionTag Provides tags that use SQL

  • ResultSupport Provides your own result sets

The tags that we've examined are ones you'll use in most cases. For more information about these classes, refer to the JavaDoc pages at http://java.sun.com/.

Creating the JAR File

Tag libraries are usually distributed as JAR files. In this section, we show you how to compile the forum application's JAR file. We provide a script file that allows you to compile the JAR file. If you are using Unix, you should use the build_taglib.sh script. If you are using Windows, you should use build_taglib.bat. The contents of both script files are essentially the same. The build_taglib.sh script file is shown in Listing 11.9.

Listing 11.9 Building the Taglib (build_taglib.sh)
javac ./com/heaton/forum/LoadUserTag.java javac ./com/heaton/forum/SaveUserTag.java javac ./com/heaton/forum/LoginUserTag.java javac ./com/heaton/forum/ExpireTag.java javac ./com/heaton/forum/NewUserTag.java javac ./com/heaton/forum/ListForumsTag.java javac ./com/heaton/forum/ListMessagesTag.java javac ./com/heaton/forum/LoadForumTag.java javac ./com/heaton/forum/PostMessageTag.java javac ./com/heaton/forum/DeleteMessageTag.java javac ./com/heaton/forum/DeleteUserTag.java javac ./com/heaton/forum/ListUsersTag.java javac ./com/heaton/forum/EditForumTag.java javac ./com/heaton/forum/EditUserTag.java javac ./com/heaton/forum/CreateForumTag.java javac ./com/heaton/forum/DeleteForumTag.java jar cvf forum.jar ./com/heaton/forum/*.class 

The script file in Listing 11.9 executes the Java compiler on each of the tag files. Once the tag files are compiled, the jar command is used to package all the class files into a single JAR file. You need to place this JAR file, named forum.jar, in the /lib directory of your Web server. The examples directory that you will download from the Sams Publishing Web site will already contain the JAR file in the /lib directory.

WARNING

If you get a compile error, you must make sure that your classpath has all of the required JAR files.


You have now learned to create tags. This includes setting up the configuration files as well as compiling the actual Java source files. Now, let's see how to use these tags.

Using the Tags from JSP

Tags are used from JSP pages. Let's look at a JSP page one of the admin pages that uses some of the tags we just created. The user administration page must present a list of all the users of the system. This page, shown in Figure 11.1, uses several of the forum application's tags.

Figure 11.1. Listing the users.

graphics/11fig01.jpg

The source code to the user administration JSP page is shown in Listing 11.10.

Listing 11.10 Our User Admin Page (users.jsp)
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jstl/core-rt" prefix="c-rt" %> <%@ taglib uri="http://java.jeffheaton.com/taglib/jstl/forum"   prefix="forum" %> <forum:expire>   <c:redirect url="index.jsp" /> </forum:expire> <c:if test="${user.type!='A'}">   <c:redirect url="main.jsp" /> </c:if> <c:if test="${param.mode=='del'}">   <forum:deleteUser  /> </c:if> <html>   <head>     <title><fmt:message key="editusers.title" bundle="${lang}"/>   </title>   </head>   <body link="#000000" vlink="#000000" alink="#000000">     <p><fmt:message key="editusers.ins" bundle="${lang}"/></p>     <forum:listUsers var="list"/>     <table border="0" width="410">       <tr>         <td bgcolor="#0000FF" width="115">           <b>             <font color="#FFFFFF" size="4">             <fmt:message key="word.action" bundle="${lang}"/></font>           </b>         </td>         <td bgcolor="#0000FF" width="45">           <b>             <font color="#FFFFFF" size="4">             <fmt:message key="word.user" bundle="${lang}"/></font>           </b>         </td>         <td bgcolor="#0000FF" width="246">           <b>             <font color="#FFFFFF" size="4">             <fmt:message key="word.type" bundle="${lang}"/></font>           </b>         </td>       </tr>       <c:forEach var="row" items="${list}"       varStatus="status">         <c:url value="${pageContext.request.requestURI}" var="del">           <c:param name="mode" value="del" />           <c:param name="target" value="${row.id}" />         </c:url>         <c:url value="edituser.jsp" var="edit">           <c:param name="mode" value="edit" />           <c:param name="target" value="${row.id}" />         </c:url>         <jsp:useBean          type="javax.servlet.jsp.jstl.core.LoopTagStatus" />         <c-rt:choose>           <c-rt:when test="<%=status.getCount()%2==0%>">             <c:set var="color" value="#FFFFFF" />           </c-rt:when>           <c-rt:otherwise>             <c:set var="color" value="#FFFF66" />           </c-rt:otherwise>         </c-rt:choose>         <tr bgcolor="<c:out value="${color}"/>">         <td width="115">           <a href="<c:out value="${del}"/>">[           <fmt:message key="word.delete" bundle="${lang}"/>]</a>           <a href="<c:out value="${edit}"/>">[           <fmt:message key="word.edit" bundle="${lang}"/>]</a>         </td>         <td width="35">           <b>             <c:out value="${row.id}" />           </b>         </td>         <td width="246">           <c:choose>             <c:when test="${row.type=='A'}"><fmt:message   key="type.a" bundle="${lang}"/></c:when>             <c:when test="${row.type=='R'}"><fmt:message   key="type.r" bundle="${lang}"/></c:when>             <c:when test="${row.type=='G'}"><fmt:message   key="type.g" bundle="${lang}"/></c:when>             <c:otherwise>               <c:out value="${row.type}" />             </c:otherwise>           </c:choose>         </td>         </tr>       </c:forEach>     </table>     <br />     <a href="admin.jsp">[<fmt:message key="word.exit"   bundle="${lang}"/>]</a>   </body> </html> 

First, the JSP page must use the appropriate import tags to gain access to the forum tag library. At the top of the JSP page, among the other tags, you can see the import tag used to import the forum tag library:

<%@ taglib uri="http://java.jeffheaton.com/taglib/jstl/forum"   prefix="forum" %> 

As you can see, the taglib statement uses the URI http://java.jeffheaton.com/taglib/jstl/forum. This is the same URI specified earlier when we created the tag library. This tag makes the forum application's tag library accessible under the prefix forum. Now that the forum tag library is accessible, we can begin to use it.

The first thing the user administration page does is ensure that the user's session is still valid. If the user session is invalid, this means that the user has been inactive for too long and has been logged off:

<forum:expire>   <c:redirect url="index.jsp" /> </forum:expire> 

Next, the page must check to see whether the user is actually an administrator. If the user is not an administrator, the user should not have made it to this page, because the page is accessible only from the main administration screen. However, it is still important to actually check to see whether the user has administrative access. This is because it is possible that the user might have directly entered the URL to this page, thus bypassing previous security. By doing this check, we make absolutely sure that this page has been entered by legitimate means and by an administrative user. This check is done using the user session variable:

<c:if test="${user.type!='A'}">   <c:redirect url="main.jsp" /> </c:if> 

At this point, we have successfully validated that the user's session still exists and that the user is a legitimate administrative user. We are now ready to perform the actual duty of this page. As previously stated, the primary purpose of this page is to display every user's login name with links that allow the administrator to delete or edit users.

To list all the users, we must first obtain a collection of every user record contained in the t_users table. We do this by using the <forum:listUsers> tag. This tag is called specifying a scoped variable in which we want to place the collection:

<forum:listUsers var="list"/> 

Now that we have access to the collection of users, we can iterate through them. We do this by using the familiar <c:forEach> tag:

<c:forEach var="row" items="${list}" varStatus="status"> 

We must display the user's ID from each of these row variables:

<td width="35">   <b>     <c:out value="${row.id}" />   </b> </td> 

As the user collection is iterated through, each user's record allows the user's ID to be displayed. In addition, we construct valid URLs for both the edit and delete links.

By using our own tags, we have now isolated the database access to our tag library. Rather than using the <sql:query> tag to access the database, we use our own tag that hides the exact table and column names.


    printer-friendly version of this section  Print  e-mail this section  E-Mail  add a public, group or private note  Add Note  add a bookmark about this section  Add Bookmark    
    Top

    [0672324504/ch11lev1sec2]

     
     


    JSTL. JSP Standard Tag Library Kick Start
    JSTL: JSP Standard Tag Library Kick Start
    ISBN: 0672324504
    EAN: 2147483647
    Year: 2001
    Pages: 93
    Authors: Jeff Heaton

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