ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 http://www.onjava.com/pub/a/onjava/2002/09/11/jstl2.html
 See this if you're having trouble printing code examples


JavaServer Pages, 2nd Edition

JSTL 1.0: What JSP Applications Need, Part 2

by Hans Bergsten, author of JavaServer Pages, 2nd Edition
09/11/2002

Part 1 of this series gave you an overview of JSTL -- the new specification of commonly-needed JSP tag libraries -- and showed you how to use the core JSTL actions. In this article, I'll dig a bit deeper and discuss how JSTL can help you with internationalization and database access. The bulk of the article does not require any Java programming knowledge, but the sections that deal with how servlets and other Java classes interact with the JSTL actions do. If you're not a Java programmer, you can just skip those sections.

Internationalization and Formatting

Large Web sites often need to please visitors from all over the world, and serving up content in just one language doesn't cut it. To develop a Web site that provides a choice of languages, you have two options:

Often you end up with a mixture of these techniques, using separate pages for mostly static, large amounts of content and shared pages when the amount of content is small but dynamic (e.g., a page with a few fixed labels displayed in different languages and all other data coming from a database).

Preparing an application for multiple languages is called internationalization (commonly abbreviated as i18n) and making content available for a specific language is called localization (or l10n). To do this right, you need to consider other things besides the language. How dates and numbers are formatted differ between countries, and even within countries. Colors, images, and other nontextual content may also need to be adapted to the customs of a certain region. The term locale refers to a set of rules and content suitable for a specific region or culture.

Locale-Sensitive Date and Number Formatting

JSTL includes a set of actions to simplify internationalization, primarily when shared pages are used for multiple languages. Let's first look at how to properly format dates and numbers. This example formats the current date and a number based on the rules for a default locale.

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>
<html>
<body>
  <h1>Formatting with the default locale</h1>
  <jsp:useBean id="now" class="java.util.Date" />
  Date: <fmt:formatDate value="${now}" dateStyle="full" />
  Number: <fmt:formatNumber value="${now.time}" />
</body>
</html>

The first line is the taglib directive for the JSTL library that contains the i18n and formatting actions. The default prefix, used here, is fmt. To get a value to format, I then create a java.util.Date object that represents the current date and time, and save it as a variable named now.

In This Series

JSTL 1.0: Standardizing JSP, Part 1 -- JSTL offers a set of standardized JSP custom actions to handle common tasks. This article, the first in a series, provides an overview and shows how to use the most common tags.

JSTL 1.0: What JSP Applications Need, Part 3 -- In the final installment in this series on JSTL Hans Bergsten, author of JavaServer Pages, 2nd Edition, shows you how to leverage the JSTL classes when developing your own custom actions.

We're now prepared to do some locale-sensitive formatting. The JSTL <fmt:formatDate> action formats the now variable value, assigned to the value attribute value using the type of EL expression you learned about in the previous article in this series. The dateStyle attribute tells the action how to format the date.

You can use any of the values default, short, medium, long, or full for the dateStyle attribute. Exactly what type of formatting these values represent varies depending on the locale. For the English locale, full results in a string like Thursday, August 29, 2002. In this example, I have only formatted the date part, but you can include the time part as well (or format just the time part), and define customized formatting rules for both the date and time instead of the locale-dependent styles:

<fmt:formatDate value="${now}" type="both" 
pattern="EEEE, dd MMMM yyyy, HH:mm" />

The pattern attribute takes a custom formatting pattern of the same type as the java.text.SimpleDateFormat class. The pattern used here results in Thursday, 29 August 2002, 17:29 with the English locale when I write this. The <fmt:formatNumber> action supports similar attributes to specify how to format a number using locale-dependent styles for a regular number, currency value, or a percentage, as well as using customized patterns of different kinds. You can learn more about all of the formatting options in the JSTL specification or in my JavaServer Pages, 2nd Edition book.


JavaServer Pages, 2nd Edition

Related Reading

JavaServer Pages, 2nd Edition
By Hans Bergsten

Table of Contents
Index
Sample Chapter

Read Online--Safari
Search this book on Safari:
 

Code Fragments only

Using JSTL to Select the Locale

Back to the main question: how is the locale for formatting the date and the number determined? If you use the JSTL actions exactly as in this example, doing nothing else, the formatting locale is determined by comparing the locales specified in a request header named Accept-Language with the locales supported by the Java runtime environment.

The header, if present, contains one or more locale specifications (in the form of a language code and possibly a country code), with information about their relative priority. The user uses browser configurations to define which locale specifications to send. The request locales are compared in priority order with the ones offered by the Java runtime, and the best match is selected.

If no match is found, the formatting action looks for the so-called fallback-locale configuration setting. A configuration setting is a value set either by a context parameter in the application's web.xml file or by a JSTL action or Java code in one of the JSP scopes. To set the fallback locale in the web.xml file, include these elements:

<context-param>
<param-name>
  javax.servlet.jsp.jstl.fmt.fallbackLocale
</param-name>
<param-value>
  de
</param-value>
</context-param>

With this setting, the German locale (specified by the de language code as the parameter value) is used if none of the locales specified by the request are supported by the Java runtime.

JSTL also lets you set a hardcoded default locale for the application that you can then override when needed. This gives you full control over which locale is used, instead of relying on the whims of the visitor's browser configuration. The default locale is also a configuration setting. It can be specified with a context parameter in the web.xml like this:

<context-param>
<param-name>
  javax.servlet.jsp.jstl.fmt.locale
</param-name>
<param-value>
  en
</param-value>
</context-param>

This setting establishes English as the default locale for the application, resulting in the previous JSP page example always formatting the date and the number according to the English rules.

To override the default locale configuration setting, you can use the <fmt:setLocale> action. It sets the default locale within a specific JSP scope. Here's an example that sets the default locale for the page scope, based on a cookie that keeps track of the locale a user selected during a previous visit:

<h1>Formatting with a locale set by setLocale</h1>
<c:if test="${!empty cookie.preferredLocale}">
<fmt:setLocale value="${cookie.preferredLocale.value}" />
</c:if>
<jsp:useBean id="now" class="java.util.Date" />
Date: <fmt:formatDate value="${now}" dateStyle="full" />
Number: <fmt:formatNumber value="${now.time}" />

Here I first use the JSTL <c:if> action to test if a cookie named preferredLocale is received with the request. If so, the <fmt:setLocale> action overrides the locale for the current page by setting the locale configuration variable in the page scope. If you want to, you can set the locale for another scope using the scope attribute. Finally, the date and the number are formatted according to the rules for the locale specified by the cookie, or the default locale, if the cookie isn't present.

In addition to formatting actions, JSTL includes actions for interpreting (parsing) dates and numbers in a locale-sensitive way: <fmt:parseDate> and <fmt:parseNumber>. These actions support pretty much the same attributes as their formatting counterparts, and can be used to convert dates and numeric input into their native form before they are further processed. I'll show you an example of this later in this article.

Using a Controller to Select the Locale

If you've developed Web applications using Java technology for some time, you have no doubt heard about the MVC pattern and, most likely, about the Apache Struts MVC framework. The basic idea behind the MVC pattern is that an application is easier to maintain and evolve if the different parts of the application (Model, View, and Controller) are implemented as separate components. For Java, this typically means using beans as the Model (business logic), JSP pages as the View (the user interface), and a servlet as the Controller (the piece that controls the communication between the View and the Model). The Struts framework provides a generic Controller servlet that delegates the processing of specific types of requests to classes called Action classes, and then uses a JSP page specified by the Action to generate the response.

JSTL is designed to play well in an MVC-based application by exposing a class that can be used by any Java component, such as a Struts Action class, to access the configuration variables used by the JSTL actions in the JSP pages representing the View. The class is called javax.servlet.jsp.jstl.core.Config and contains constants (static final String variables) for all configuration variables, and methods for setting, getting, and removing the variables in different JSP scopes. You can use code like this in a Struts Action class to set the default locale for the session, based on profile data, when a user logs in to the application:

import javax.servlet.jsp.jstl.core.Config;
...
public class LoginAction extends Action {
public ActionForward perform(ActionMapping mapping, 
  ActionForm form, HttpServletRequest request, 
  HttpServletResponse response) 
  throws IOException, ServletException {

  String userName = request.getParameter("userName");
  String password = request.getParameter("password");
  ...
  User user = authenticate(userName, password);
  if (user != null) {
	/*
	 * Valid user. Set the default locale in the session
	 * to the user's preferred locale
	 */
	HttpSession session = request.getSession();
	Config.set(session, Config.FMT_LOCALE, user.getLocale());
	...
}    
}

Related Reading

Programming Jakarta Struts

Programming Jakarta Struts
By Chuck Cavaness

Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:
 

Code Fragments only

The Config class provides four versions of the set() method, one for each JSP scope (specified by the first method parameter). Here I use the one that sets the variable in the session scope. The second parameter is the name of the configuration, typically set by the corresponding constant. The third parameter is the variable value. For reading and removing variables, the class provides similar get() and remove() methods, plus a find() method for locating a variable in any scope. In addition to using these methods in a Controller, you may want to use them in your own custom tag handlers to take advantage of the JSTL configuration setting mechanism.

Generating Localized Text

While date and number formatting is important when localizing an application, the text content is, of course, even more so. JSTL is based on Java, so it leverages the generic i18n support in the Java platform. When it comes to text, this support is based on what's called a resource bundle. In its simplest form, a resource bundle is represented by a text file containing keys and a text value for each key. This example shows a file with two keys (hello and goodbye) and their values:

hello=Hello
goodbye=Goodbye

Multiple locales are supported by creating separate files for each locale, with a filename that includes the locale name. For instance, if the resource bundle file above represents the English locale, it may be stored in a file named labels_en.properties. The resource bundle for the Swedish locale would then be stored in a file named labels_sv.properties, where sv is the language code for Swedish. The fixed part of the file name, labels in this example, is called the resource bundle base name, and it's combined with a locale specification (such as a language code) to find the resource bundle for a specific locale. All resource bundle files include the same keys; only the text differs depending on the locale. The files must be located in a directory that's part of the Web container's classpath, typically the application's WEB-INF/classes directory.

The JSTL action that adds text from a resource bundle to a page is the <fmt:message> action. The bundle to use can be specified in a number of ways. One way is to nest the <fmt:message> actions within the body of a <fmt:bundle> action:

<fmt:bundle basename="labels">
Hello: <fmt:message key="hello" />
Goodbye: <fmt:message key="goodbye" />
</fmt:bundle>

In this case, the <fmt:bundle> action locates the bundle for the locale that is the closest match between the locale configuration setting (or the locales in the Accept-Language header, if no default locale is set) and the available resource bundles for the specified base name. The nested actions then get the text from this bundle for the specified key.

As with the formatting actions, you can also establish a default bundle, either for the whole application with a context parameter or with the <fmt:setBundle> action or the Config class for a specific JSP scope:

<fmt:setBundle basename="labels"/>
...
Hello: <fmt:message key="hello" />
Goodbye: <fmt:message key="goodbye" />

After a default bundle has been defined, you can use the <fmt:message> actions standalone within the scope where the default is established.

There are more options for the i18n and formatting actions than I can fit into this article. For instance, you can use messages that contain dynamic values assigned using nested <fmt:param> actions, and override which bundle to use within a certain context. Another important area when it comes to i18n is how to handle languages with non-Western characters. I describe all of this and more in JavaServer Pages, 2nd Edition.

Accessing a Database

A somewhat controversial subject is JSTL's inclusion of actions for accessing a database. Some people see this as encouraging bad practices, and argue that all database access should be performed by pure Java components in an MVC-based application instead of by JSP pages. I agree with this point of view for anything but the simplest applications, but there are a lot of applications that qualify as very simple, and where lack of programming skills or time makes a full-blown MVC architecture impractical. Without JSTL support, these applications often end up with Java database access code in scriptlets instead, and that is far worse from both a development and maintenance standpoint. I'll therefore show you how to use JSTL to access a database in JSP pages, but ask you to keep in mind that this approach is not suitable for all types of applications. If your development team includes Java programmers, you should carefully consider encapsulating the database access code in Java classes instead, and use JSP only to display the result.

The JSTL database actions are based on the general Java database API (JDBC) and use the javax.sql.DataSource abstraction introduced in JDBC 2.0 to represent the database. A DataSource provides connections to the database and can implement a feature called connection pooling. Opening a physical connection to a database is a very time-consuming operation. With connection pooling, it only needs to be done once, and the same connection can then be reused over and over, without risking problems associated with other connection-sharing approaches.

Making a DataSource Available to JSTL

JSTL supports a number of ways to make a DataSource available to the database actions. In a Web container with Java Naming and Directory Interface (JNDI) support, a default DataSource can be defined as a JNDI resource with a context parameter in the web.xml file:

<context-param>
<param-name>
  javax.servlet.jsp.jstl.sql.dataSource
</param-name>
<param-value>
  jdbc/Production
</param-value>
</context-param>

The Web container's JNDI configuration tools must be used to configure a JNDI resource with the specified name; for instance, with a database account username and password, min and max connections in the pool, etc. How this is done varies between containers and is out of scope for this article (I cover, in detail, how to do it for Tomcat 4 in JavaServer Pages, 2nd Edition, though).

An alternative for containers that do not support JNDI is to let an application (servlet context) lifecycle listener create and configure a DataSource and set it as the default using the JSTL Config class:

import javax.servlet.*;
import javax.servlet.http.*;
import oracle.jdbc.pool.*;

public class AppListener implements ServletContextListener {

private OracleConnectionCacheImpl ds =null;

public void contextInitialized(ServletContextEvent sce){
  ServletContext application =sce.getServletContext();

  try {
	ds = new OracleConnectionCacheImpl();
	ds.setURL("jdbc:oracle:thin:@voyager2:1521:Oracle9i");
	ds.setMaxLimit(20);
	ds.setUser("scott");
	ds.setPassword("tiger");
  }
  catch (Exception e){
	application.log("Failed to create data source:"+
	e.getMessage());
  }
  Config.SQL_DATA_SOURCE;
}
...
}

The listener class in this example creates a DataSource with connection pool capabilities for an Oracle9i database, and makes it available as the default for the JSTL actions by using the Config class to set the corresponding configuration variable.

A third way, only suitable for prototyping or applications that are not so heavily used as to need connection pooling, is to use the <sql:setDataSource> action:

<sql:setDataSource
url="jdbc:mysql://dbserver/dbname"
driver="org.gjt.mm.mysql.Driver"
user="scott"
password="tiger" />

This action creates a simple data source, without pooling, for the specified JDBC URL, user and password, using the specified JDBC driver. You may use this action to get started, but I recommend that you use one of the other alternatives for a real site. Besides the lack of connection pooling for a data source created this way, it's not a good idea to include sensitive information like the database URL, username and password in a JSP page, since it may be possible for a Bad Guy to get access to the source of the page. Even though it shouldn't be possible, several bugs have been discovered in Web containers over the years that made this possible (as far as I know, all of them have been plugged in recent versions of the most commonly-used containers).

Reading Database Data

With a DataSource available, we can access the database. Here's how you read data from a database represented by the default DataSource:

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

<html>
<body>
  <h1>Reading database data</h1>
  <sql:query var="emps" sql="SELECT * FROM Employee" />
  ...
</body>
</html>

First you need to declare the JSTL library that contains the database actions, using the taglib directive at the top of this example. The <sql:query> action executes the SQL SELECT statement specified by the sql attribute (or as the body of the action element) and saves the result in the variable named by the var attribute.

The database query result is returned as a bean of the type javax.servlet.jsp.jstl.sql.Result with a number of read-only properties:

Property Java Type Description
rows java.util.SortedMap[] An array with a case-insensitive map per row with keys matching column names and values matching column values.
rowsByIndex Object[][] An array with an array per row with column values.
columnNames String[] An array with column names.
rowCount int The number of rows in the result.
limitedByMaxRows boolean true if not all matching rows are included due to reaching a specified max rows limit.

I showed you how to use the JSTL <c:forEach> action to display all or just some of the rows in part 1 of this article, so let's see how you can get just some of the rows and display them all in this part. Next and Previous links allow the user to ask for a different set. First, here's how to read a subset of the rows and then display the complete subset:

<c:set var="noOfRows" value="10" />

<sql:query var="emps" 
startRow="${param.start}" maxRows="${noOfRows}">
SELECT * FROM Employee
</sql:query>

<ul>
<c:forEach items="${emps.rows}" var="${emp}">
  <li><c:out value="${emp.name}" />
</c:forEach>
</ul>

The startRow attribute for the <sql:query> action is set to an EL expression that reads the value of a request parameter named start. You'll soon see how its value changes when clicking on the Next and Previous links. The first time the page is accessed, the parameter is not present at all, so the expression evaluates to 0. This means the query result contains rows starting with the first matching row (index 0). The maxRows attribute limits the total number of rows to the value of the noOfRows variable, set to 10 in this example. The <c:forEach> action loops through all rows in the result and generates a list item with one of the column values for each row.

We must also generate Next and Previous links to let the user grab a new set of rows:

<c:choose>
<c:when test="${param.start > 0}">
  <a href="emplist.jsp?start=<c:out 
	value="${param.start - noOfRows}"/>">Previous Page</a>
</c:when>
<c:otherwise>
  Previous Page
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${emps.limitedByMaxRows}">
  <a href="emplist.jsp?start=<c:out
	value="${param.start + noOfRows}"/>">Next Page</a>
</c:when>
<c:otherwise>
  Next Page
</c:otherwise>
</c:choose>

The first <c:choose> block is identical to the example in part 1; if the start request parameter is greater than zero, the current page shows a row subset other than the first, so a Previous link is added. The link points back to the same page, and includes the start parameter with a value that is its current value minus the number of rows displayed per page.

The second <c:choose> block takes advantage of the query result's limitedByMaxRows property. If this property is true, it means that the current result is truncated to the number of rows displayed per page. Hence, a Next link is generated with a start parameter value for the next row subset.

Writing Database Data

Besides reading data from a database, you can use JSTL to update information, as well. This example shows how to insert a new row in a table:

<c:catch var="error">
<fmt:parseDate var="empDate" value="${param.empDate}" 
  pattern="yyyy-MM-dd" />
</c:catch>
<c:if test="${error != null}">
<jsp:useBean id="empDate" class="java.util.Date" />
</c:if>

<sql:update>
INSERT INTO Employee (FirstName, LastName, EmpDate)
  VALUES(?, ?, ?)
<sql:param value="${param.firstName}" />
<sql:param value="${param.lastName}" />
<sql:dateParam value="${empDate}" type="date" />
</sql:update>

Before inserting the row, this example illustrates how to use the JSTL parsing actions, as I promised earlier. The page expects all data for the new row to be sent as request parameters (perhaps entered in an HTML form), including an employment date. Before the date can be inserted into the database, it must be converted to its native Java form. That's what the <fmt:parseDate> action does. The value attribute contains an EL expression that gets the value of the empDate request parameter. The action tries to interpret it as a date written in the format specified by the pattern attribute (a four-digit year followed by a two-digit month and a two-digit day, separated by dashes). If it's successful, it stores the date in its native form in a variable with the name specified by the var attribute.

The <c:catch> action takes care of invalid date strings. If the parameter value can not be interpreted as a date, the <fmt:parseDate> throws an exception, which the <c:catch> action catches and saves in the specified variable. When this happens, the <c:if> action's test condition evaluates to true, so the variable for the employment date is instead created by the nested <jsp:useBean> action.

To insert the row, you use the <sql:update> action. As with the query action, the SQL statement can be specified as the element's body or by an sql attribute. The <sql:update> action can be used to execute INSERT, UPDATE, and DELETE statements, as well as statements that create or remove database objects, such as CREATE TABLE and DROP TABLE. The number of rows affected by the statement can optionally be captured in a variable named by a var attribute.

In this example (as in most real-world applications), the column values are not known until runtime; they come from request parameters. The SQL INSERT statement therefore includes one question mark per value as a placeholder and nested parameter actions that set the value dynamically. The FirstName and LastName columns are text columns, and <sql:param> actions set their values to the values of the corresponding request parameters.

The EmpDate column, however, is a date column, demanding special attention. First of all, you must use a variable that holds a date in its native form (a java.util.Date object), so instead of using the request parameter value, we use the variable created by the <fmt:parseDate> or <jsp:useBean> actions. Second, you must use the <sql:dateParam> action to set the value. In this example, I'm using only the date part, so I also set the optional type attribute to date. Other valid values are time and timestamp (the default), for columns that take only the time or both the date and time.

There's one more JSTL database action that I have not described so far: <sql:transaction>. You can use this action to group multiple update (or even query) actions when they must all be executed as part of the same database transaction. The standard example is transferring an amount of money from one account to another, implemented as one SQL statement that removes the money from the first account and another statement that adds it to the other. The JSTL specification and my book include detailed examples of how to use transactions.

If you encapsulate all database access in Java classes instead of using the JSTL database action, there's still one part of JSTL that you may find useful. It's a class named javax.servlet.jsp.jstl.sql.ResultSupport, with these two methods:

public static Result toResult(java.sql.ResultSet rs);
public static Result toResult(java.sql.ResultSet rs, int maxRows);

You can use this class to turn a standard JDBC ResultSet object into a JSTL Result object before forwarding it to a JSP page for display. The JSTL actions can easily access the data in a Result object, as shown earlier. Another, arguably better, approach is to pass the query result to the JSP page as a custom data structure, such as a List of beans that contain the data for each row, but the Result object is still a good candidate for prototypes and small applications.

Conclusion

In this installment, I have described the JSTL support for internationalization and database access, including how some of the resources used by these actions can be configured through the web.xml file, JNDI, and servlets, letting other components of an MVC-based application interact with the JSTL actions. It's impossible to discuss everything about these subjects in detail in an article, but I hope that what I've described is enough to get you started.

In the next part, we will look at how classes defined by the JSTL specification can be used to simplify development of your own custom actions.

Resources

Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.


Return to ONJava.com.

Copyright © 2004 O'Reilly Media, Inc.