![]() |
JSTL 1.0: What JSP Applications Need, Part 2by Hans Bergsten, author of JavaServer Pages, 2nd Edition09/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.
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.
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.
![]() |
Related Reading |
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.
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 ![]() |
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.
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.
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.
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).
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.
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.
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.
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.