![]() |
JSTL 1.0: Standardizing JSP, Part 1by Hans Bergsten, author of JavaServer Pages, 2nd Edition08/14/2002 |
June 11, 2002 started a new phase for JSP developers. That's when the JSP Standard Tag Library (JSTL) 1.0 specification was released. The Apache Taglibs project followed up with a reference implementation a few days later.
JSTL answers developers' demand for a set of standardized JSP custom actions to handle the tasks needed in almost all JSP pages, including conditional processing, internationalization, database access, and XML processing. This will speed up JSP development by more or less eliminating the need for scripting elements and the inevitable hard-to-find syntax errors, and by freeing up time previously spent on developing and learning zillions of project-specific custom actions for these common tasks.
This article is the first in a series of articles about how JSTL can simplify your life when using JSP, in applications large and small. In this article, I give you an overview of JSTL and show you how to use the most common JSTL actions. Future installments will focus on the internationalization and database access actions, how a servlet controller can interact with these actions, and how to use the JSTL classes as a base for your own custom actions. If you have suggestions for other areas you'd like to learn more about, please let me know.
JSTL 1.0 specifies a set of custom tag libraries based on the JSP 1.2 API. There are four separate tag libraries, each containing custom actions targeting a specific functional area. This table lists each library with its recommended tag prefix and default URI:
Description | Prefix | Default URI |
---|---|---|
Core | c |
http://java.sun.com/jstl/core |
XML Processing | x |
http://java.sun.com/jstl/xml |
I18N & Formatting | fmt |
http://java.sun.com/jstl/fmt |
Database Access | sql |
http://java.sun.com/jstl/sql |
The Core library contains actions for everyday tasks, such as including or excluding a piece of a page depending on a runtime condition, looping over a collection of items, manipulating URLs for session tracking, and correct interpretation by the target resource, as well as actions for importing content from other resources and redirecting the response to a different URL.
In This Series JSTL 1.0: What JSP Applications Need, Part 2 -- Part 2 of our JSTL series focuses on internationization, localization, and database access. 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. |
The XML library contains actions for -- you guessed it -- XML processing, including parsing an XML document and transforming it using XSLT. It also provides actions for extracting pieces of a parsed XML document, looping over a set of nodes, and conditional processing based on node values.
Internationalization (i18n) and general formatting are supported by the actions in the I18N & Formatting library. You can read and modify information stored in a database with the actions provided by the Database Access library.
Over time, you can expect all Web containers to include an implementation of
the JSTL libraries, so no additional code will need to be installed. Until that
happens, you can download and install the JSTL reference implementation (RI)
instead. It's developed within the Apache Taglibs project as a library named
Standard. The link to the Standard library page is included in the Resources
section at the end of this article. Installing the RI is easy: just copy all
of the JAR files from the distribution's lib
directory to the
WEB-INF/lib
directory for your Web application. Note that JSTL 1.0
requires a JSP 1.2 container, so make sure your container is JSP-1.2-compliant
before you try this.
![]() |
Related Reading |
To use a JSTL library, whether it's the implementation included with the
container or the RI, you must declare the library using a taglib
directive, just as you would for a regular custom tag library:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
Note that you should always use the default URI, even though the JSP specification allows you to override it. A container is allowed to generate optimized code for the JSTL action in the class corresponding to a JSP page. This can result in better performance than when the generated code calls out to tag handlers through the standard API. It's only when you use the default URI, however, that a container is able to use an optimized implementation.
In addition to the tag libraries, JSTL 1.0 defines a so-called Expression Language (EL). The EL is a language for accessing runtime data from various sources. Its syntax is considerably more user-friendly than Java, which is the only language supported directly by the JSP 1.2 specification. All JSTL actions recognize EL expressions in their attribute values, and custom actions may be developed to do the same. It is expected that the EL will be incorporated into the next version of the JSP specification to encourage its use for data access over the Java language. If so, you will be able to use EL expressions in an action attribute value, and even in template text.
If you've used JavaScript, you should feel right at home with the EL. The EL
borrows the JavaScript syntax for accessing structured data as either a
property of an object (with the .
operator) or as a named
array element (with the ["name"]
operator). JavaBeans
component properties and java.util.Map
entries, using the key as
the property name, can be accessed this way. Here are some examples:
${myObj.myProperty}$
${myObj["myProperty"]}$
${myObj[varWithTheName]}$
As shown here, an EL expression must always be enclosed within
${
and }
characters. The first two expressions access
a property named myProperty
in an object represented by a variable
named myObj
. The third expression access a property with a name
that's held by a variable. Instead of a single variable, this syntax can be used
with any expression that evaluates to the property name.
The array access operator is also used for data represented as a collection
of indexed elements, such as a Java array or a
java.util.List
:
${myList[2]}$
${myList[aVar + 1]}$
In addition to the property and array element operators and the arithmetic, relational, and logical operators, a special operator for testing if an object is "empty" or not can be used in an EL expression. The following table lists all operators:
Operator | Description |
---|---|
. |
Access a property |
[] |
Access an array/list element |
() |
Group a subexpression |
+ |
Addition |
- |
Subtraction or negation of a number |
/ or div |
Division |
% or mod |
Modulo (remainder) |
== or eq |
Test for equality |
!= or ne |
Test for inequality |
< or lt |
Test for less than |
> or gt |
Test for greater than |
<= or le |
Test for less than or equal |
>= or gt |
Test for greater than or equal |
&& or and |
Test for logical AND |
|| or or |
Test for logical OR |
! or not |
Unary Boolean complement |
empty |
Test for empty value (null, empty string, or an empty collection) |
What you don't find in the EL are statements such as assignments,
if/else
, or while
. Action elements are used for this
type of functionality in JSP, and the EL is not intended to be a general-purpose
programming language, just a data access language.
Literals and variables are, of course, also part of the language. The EL provides the following literals, similar to what you find in JavaScript, Java, and other languages:
Literal Type | Description |
---|---|
String | Enclosed with single or double quotes. A quote of the same type within
the string must be escaped with backslash: (\' in a string
enclosed with single quotes; \" in a string enclosed with
double quotes). The backslash character must be escaped as \\
in both cases. |
Integer | An optional sign (+ or -) followed by digits between 0 and 9. |
Floating Point | The same as an integer literal, except that a dot is used as the
separator for the fractional part and an exponent can be specified as
e or E , followed by an integer literal. |
Boolean | true or false . |
Null | null . |
Any object in one of the JSP scopes (page, request, session, or application)
can be used as a variable in an EL expression. For instance, if you have an bean
with a firstName
property in the request scope under the name
customer
, this EL expression represents the value of that bean's
firstName
property:
${customer.firstName}
But it doesn't stop there. The EL also makes request information and general container information available as a set of implicit variables:
Variable | Description |
---|---|
param |
A collection of all request parameters as a single string value for each parameter. |
paramValues |
A collection of all request parameters as a string array value for each parameter. |
header |
A collection of all request headers as a single string value for each header. |
headerValues |
A collection of all request headers as a string array value for each header. |
cookie |
A collection of all request cookies as a single
javax.servlet.http.Cookie instance value for each
cookie. |
initParams |
A collection of all application init parameters as a single string value for each parameter. |
pageContext |
An instance of the javax.servlet.jspPageContext
class. |
pageScope |
A collection of all page scope objects. |
requestScope |
A collection of all request scope objects. |
sessionScope |
A collection of all session scope objects. |
applicationScope |
A collection of all application scope objects. |
The first five implicit variables in the table give you access to the
parameter values, headers, and cookies for the current request. Here's an
example of how to access a request parameter named listType
and the
User-Agent
header:
${param.listType}
${header['User-Agent']}
Note how you must use the array syntax for the header, because the name
includes a dash; with the property syntax, it would be interpreted as the value
of the variable expression header.User
minus the value of the
variable named Agent
.
The initParameter
variable provides access to init parameters
that are defined for the application in the web.xml
file. The
pageContext
variable has a number of properties that provide access
to the servlet objects that represent the request, response, session,
application, etc. Look at the JSP specification to learn more about these
properties.
The final four variables are collections containing all objects in each
specific scope. You can use these to limit the search for an object to just one
scope instead of searching all scopes, which is the default if no scope is
specified. In other words, if there's an object named customer
in
the session scope, the first two expressions here find the same object and the
third comes up empty:
${customer}
${sessionScope.customer}
${requestScope.customer}
All JSTL actions accept EL expressions as attribute values, for all
attributes except var
and scope
, because these
attribute values may be used for type checking at translation time in a future
version. There's one additional JSTL action attribute that does not take an EL
expression value, but it's only used in the XML library, so let's ignore that
for now. One or more EL expressions can be used in the same attribute value, and
fixed text and EL expressions can be mixed in the same attribute value:
First name: <c:out value="${customer.firstName}" />
<c:out value="First name: ${customer.firstName}" />
Before we jump in and look at examples using the Core actions, let me qualify something I said earlier: all JSTL actions in the EL library set accept EL expressions. There's actually a parallel set of JSTL libraries, referred to as the RT library set, that only accept the old-style Java expressions:
First name: <c_rt:out value="<%= customer.getFirstName() %>" />
I encourage you to use the EL libraries instead, but if you're curious, you can read about the RT libraries in the JSTL spec or in my book, JavaServer Pages (O'Reilly, 2nd edition 2002).
Let's look at some examples of how you can use the JSTL conditional and
iteration actions: <c:if>
; the <c:choose>
,
<c:when>
, and <c:otherwise>
triple; and
<c:forEach>
. Along the way, we also use the basic output and
variable setting actions: <c:out>
and
<c:set>
.
<c:if>
allows you to conditionally include, or process, a
piece of the page, depending on runtime information. This sample includes a
personal greeting if the user is a repeat visitor, as indicated by the presence
of a cookie with the user's name:
<c:if test="${!empty cookie.userName}">
Welcome back <c:out value="${cookie.userName.value}" />
</c:if>
The test
attribute value is an EL expression that checks if the
cookie is present. The empty
operator combined with the "not"
operator (!
) means it evaluates to true
if the cookie
is not present, causing the element body to be processed. Within the body, the
<c:out>
action adds the value of the cookie to the response.
It's that easy.
Looping through a collection of data is almost as simple. This snippet iterates through a collection of rows from a database with weather information for different cities:
<c:forEach items="${forecasts.rows}" var="city">
City: <c:out value="${city.name}" />
Tomorrow's high: <c:out value="${city.high}" />
Tomorrow's low: <c:out value="${city.low}" />
</c:forEach>
The EL expression for the items
value gets the value of the
rows
property from an object represented by the
forecasts
variable. As you will learn in future articles, the JSTL
database actions represent a query result as an instance of a class named
javax.servlet.jsp.jstl.sql.Result
. This class can be used as a bean
with a number of properties. The rows
property contains an array of
java.util.SortedMap
instances, each one representing a row with
column values. The <c:forEach>
action processes its body once
for each element in the collection specified by the items
attribute. Besides arrays, the action works with pretty much any data type that
represents a collection, such as instances of java.util.Collection
and java.util.Map
.
If the var
attribute is specified, the current element of the
collection is made available to actions in the body as a variable with the
specified name. Here it's named city
and, since the collection is
an array of maps, this variable contain a new map with column values every time
the body is processed. The column values are added to the response by the same
type of <c:out>
actions that you saw in the previous example.
To illustrate the use of the remaining conditional actions, let's extend the
iteration example to only process a fixed set of rows for each page request, and
add "Previous" and "Next" links back to the same page. The user can then scroll
through the database result, looking at a few rows at a time, assuming the
Result
object is saved in the session scope. Here's how to only
process some rows:
<c:set var="noOfRows" value="10" />
<c:forEach items="${forecasts.rows}" var="city"
begin="${param.first}" end="${param.first + noOfRows - 1}">
City: <c:out value="${city.name}" />
Tomorrow's high: <c:out value="${city.high}" />
Tomorrow's low: <c:out value="${city.low}" />
</c:forEach>
The <c:set>
action sets a variable to the value specified
by the value
attribute; either a static value, as in this example,
or an EL expression. You can also specify the scope for the variable with the
scope
attribute (page
, request
,
session
or application
). In this example, I set a
variable named noOfRows
to 10 in the page scope (the default). This
is the number of rows to show for each request.
The <c:forEach>
in this example takes the same values for
the items
and var
attributes as before, but I have
added two new attributes.
The begin
attribute takes the 0-based index of the first
collection element to process. Here it's set to the value of a request
parameter named first
. For the first request, this parameter is
not available, so the expression evaluates to 0; in other words, the first
row.
The end
attribute specifies the index for the last collection
element to process. Here I set it to the value of the first
parameter plus noOfRows
minus one. For the first request, when
the request parameter is missing, this results in 9, so the action loops over
indexes 0 through 9.
Next we add the "Previous" and "Next" links:
<c:choose>
<c:when test="${param.first > 0}">
<a href="foreach.jsp?first=<c:out value="${param.first - noOfRows}"/>">
Previous Page</a>
</c:when>
<c:otherwise>
Previous Page
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${param.first + noOfRows < forecasts.rowsCount}">
<a href="foreach.jsp?first=<c:out value="${param.first + noOfRows}"/>">
Next Page</a>
</c:when>
<c:otherwise>
Next Page
</c:otherwise>
</c:choose>
The <c:choose>
groups one or more
<c:when>
actions, each specifying a different Boolean
condition. The <c:choose>
action tests each condition in
order and allows only the first <c:choose>
action with a
condition that evaluates to true
to process its body. The
<c:choose>
body may also contain a
<c:otherwise>
. Its body is processed only if none of the
<c:when>
actions' conditions are true
.
In this example, the first <c:when>
action tests if the
first
parameter is greater than 0, i.e. if the page displays a row
subset other than the first. If that's true, the <c:when>
action's body adds a link back to the same page with the first
parameter set to the index of the previous subset. If it's not true, the
<c:otherwise>
action's body is processed, adding just the
text "Previous Page" as a placeholder for the link. The second
<c:choose>
block provides similar logic for adding the "Next
Page" link.
The previous example works fine as long as cookies are used for session tracking. That's not a given; cookies may be turned off, or not supported, in the browser. It's therefore a good idea to enable the container to use URL rewriting as a backup for cookies. URL rewriting, as you may know, means putting the session ID in all URLs used in links and forms in the page. A rewritten URL looks something like this:
myPage.jsp;jspsessionid=ah3bf5e317xmw5
When the user clicks on a link like this, the session ID is sent to the
container as part of the URL. The JSTL Core library includes the
<c:url>
action, which takes care of URL rewriting for you.
This is how you can use it to improve the generation of the "Previous Page" link
from the previous example:
<c:url var="previous" value="foreach.jsp">
<c:param name="first" value="${param.first - noOfRows}" />
</c:url>
<a href="<c:out value="${previous}"/>">Previous Page</a>
The <c:url>
supports a var
attribute, used to
specify a variable to hold the encoded URL, and a value
attribute
for the URL to be encoded. URL query string parameters can be specified using
nested <c:param>
actions. Special characters in the
parameters specified by nested elements are encoded (if needed) and then added
to the URL as query string parameters. The final result is passed through the
URL rewriting process, adding a session ID if cookie-based session tracking is
disabled. In this example, the fully encoded URL is then used as the
href
attribute value in the HTML link element.
The <c:url>
also performs another nice service. As you may
be aware, relative URLs in HTML elements must either be relative to the page
that contains them or to the root directory of the server (if they start with a
slash). The first part of a URL path for a JSP page is called the context
path, and it may vary from installation to installation. You should
therefore avoid hard-coding the context path in the JSP pages. But sometimes you
really want to use a server-relative URL path in HTML elements; for instance
when you need to refer to an image file that's located in an
/images
directory shared by all JSP pages, no matter where in the
document structure the page reside. The good news is that if you specify a URL
starting with a slash as the <c:url>
value, it converts it to
a server-relative path. For instance, in an application with the context path
/myApp
, the <c:url>
action converts the path to
/myApp/images/logo.gif
:
<c:url value="/images/logo.gif" />
There are a few more actions related to URLs in the Core library. The
<c:import>
action is a more flexible action than the standard
<jsp:include>
action. You can use it to include content from
resources within the same Web application, from another Web application in the
same container, or from another server, using protocols like HTTP and FTP. The
<c:redirect>
action lets you redirect to another resource in
the same Web application, in another Web application, or on a different server.
Both actions are straightforward to use, so I leave it as an exercise for you to
try them out.
In this installment, I have described the JSTL basic building blocks: the set of libraries and the Expression Language. I have also provided examples of how to use most of the actions in the Core library. You can download the JSTL reference implementation and use it with any JSP-1.2-compatible Web container to experiment with these actions. The RI includes a number of examples to help you get started.
In upcoming articles, we will look at the remaining JSTL libraries, including configuration options and how to control their behavior from a controller servlet when using an MVC framework such as Struts. I will also show you how classes defined by the JSTL specification can be used to simplify developing 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.