10.1 Passing Control and Data
Between Pages
As discussed in Chapter
3, one of the most fundamental features of JSP technology
is that it allows for separation of request processing,
business logic and presentation, using what's known as the
Model-View-Controller (MVC) model. As you may recall, the
roles of Model, View, and Controller can be assigned to
different types of server-side components. In this part of the
book, JSP pages are used for both the Controller and View
roles, and the Model role is played by either a bean or a JSP
page. This isn't necessarily the best approach, but it lets us
focus on JSP features instead of getting into Java
programming. If you're a programmer and interested in other
role assignments, you may want to take a peek at Chapter
17 and Chapter
18. These chapters describe other alternatives and focus
on using a servlet as the Controller.
In this section we look at how to separate
the different aspects in a pure JSP application, using a
modified version of the User Info example from Chapter
8 as a concrete example. In this application, the business
logic piece is trivial. However, it sets the stage for a more
advanced application example in the next section and the
remaining chapters in this part of the book; all of them use
the pattern introduced here.
The different aspects of the User Info example can be categorized like
this:
-
Display the form for user input
(presentation)
-
Validate the input (request processing and
business logic)
-
Display the result of the validation
(presentation)
A separate JSP page is used for each aspect
in the modified version. The restructured application contains
the three JSP pages shown in Figure
10-1.
Here's how it works. The
userinfoinput.jsp page displays an input form. The user
submits this form to userinputvalidate.jsp to validate
the input. This page processes the request using the
UserInfoBean and passes control to either the
userinfoinput.jsp page (if the input is invalid) or the
userinfovalid.jsp page (if the input is valid). If
valid, the userinfovalid.jsp page displays a "thank
you" message. In this example, the UserInfoBean
represents the Model, the userinputvalidate.jsp page
the Controller, and userinfoinput.jsp and
userinfovalid.jsp represent the Views.
This gives you the flexibility and
maintainability discussed in Chapter
3. If the validation rules change, a Java programmer can
change the UserInfoBean implementation without
touching any other part of the application. If the customer
wants a different look, a page author can modify the View JSP
pages without touching the request processing or business
logic code.
Using different JSP pages as Controller and
View means that more than one page is used to process a
request. To make this happen, you need to be able to do two
things:
10.1.1 Passing Control from One
Page to Another
Before digging into the
modified example pages, let's go through the basic mechanisms
for satisfying the two requirements. As shown in Figure
10-1, the userinfovalidate.jsp page passes control
to one of two other pages based on the result of the input
validation. JSP supports this through the <jsp:forward> action,
described in Table
10-1. <jsp:forward page="userinfoinput.jsp" />
Table 10-1. Attributes for
<jsp:forward>
page |
String |
Yes, but only the scripting kind (see
Chapter
15) |
Mandatory. A page-relative or
context-relative path for the target resource.
|
The <jsp:forward> action stops
processing of one page and starts processing the page
specified by the page attribute instead, called the target
page. The control never returns to the original page.
The target page has access to all information
about the request, including all request parameters. You can
also add additional request parameters when you pass control
to another page by using one or more nested
<jsp:param> action elements (see Table
10-2): <jsp:forward page="userinfoinput.jsp" >
<jsp:param name="msg" value="Invalid email address" />
</jsp:forward>
Table 10-2. Attributes for
<jsp:param>
name |
String |
No |
Mandatory. The parameter
name. |
value |
String |
Yes, but only the scripting kind (see
Chapter
15) |
Mandatory. The parameter
value. |
Parameters specified with
<jsp:param> elements are added to the
parameters received with the original request. The target
page, therefore, has access to both the original parameters
and the new ones, and can access both types in the same way.
If a parameter is added to the request using a name of a
parameter that already exists, the new value is added first in
the list of values for the parameter.
The page attribute is interpreted
relative to the location of the current page if it doesn't
start with a /. This called a page-relative path. If the source and
target page are located in the same directory, just use the
name of the target page as the page attribute value,
as in the previous example. You can also refer to a file in a
different directory using notation such as
../foo/bar.jsp or /foo/bar.jsp. When the page
reference starts with a /, it's interpreted relative
to the top directory for the application's web page files.
This is called a context-relative
path.
Let's look at some concrete examples to make
this clear. If the application's top directory is
C:\Tomcat\webapps\myapp, page references in a JSP page
located in
C:\Tomcat\webapps\myapp\registration\userinfo are
interpreted like this:
- page="bar.jsp"
-
C:\Tomcat\webapps\myapp\registration\userinfo\bar.jsp
- page="../foo/bar.jsp"
-
C:\Tomcat\webapps\myapp\registration\foo\bar.jsp
- page="/foo/bar.jsp"
-
C:\Tomcat\webapps\myapp\foo\bar.jsp
Note that even though Table
10-1 and Table
10-2 show you can use a dynamic value for the
<jsp:forward> page attribute and the
<jsp:param> value attribute, you can't use an
EL expression. The reason for this is discussed in Chapter
15, and alternatives to these two actions that support EL
expressions (<ora:forward> and
<ora:param>) are introduced later in this book.
10.1.2 Passing Data from One Page
to Another
JSP provides different scopes for sharing data objects
between pages, requests, and users. The scope defines how long
the object is available and whether it's available only to one
user or to all application users. The following scopes are
defined: page, request, session, and application.
Objects placed in the default scope, the
page scope, are available only within that page.
That's the scope used in all examples you have seen so far.
The request scope is for objects that need to be available
to all pages processing the same request. Objects in the session scope
are available to all requests made from the same browser, and
objects in the application
scope are shared by all users
of the application (see Figure
10-2). According to the JSP specification, the name used
for an object must be unique within all scopes. This means
that if you have an object named userInfo in the
application scope, for instance, and save another object with
the same name in the request scope, the container may remove
the first object. Few containers (if any) enforce this rule,
but you should ensure you use unique names anyway to avoid
portability problems.
The <jsp:useBean> action has a
scope attribute you use to specify the scope for the
bean. Here is an example: <jsp:useBean id="userInfo" scope="request"
class="com.ora.jsp.beans.userinfo.UserInfoBean" />
The <jsp:useBean> action
ensures that the bean already exists in this scope or that a
new one is created and placed in the specified scope. It first
looks for a bean with the name specified by the id
attribute in the specified scope. If it already exists, for
instance created by a previously invoked
<jsp:useBean> action or by a servlet, it does
nothing. If it can't
find it, it creates a new instance of the class specified by
the class attribute and makes it available with the
specified name within the specified scope.
If you'd like to perform an action only when
the bean is created, place the elements in the body of the
<jsp:useBean> action: <jsp:useBean id="userInfo" scope="request"
class="com.ora.jsp.beans.userinfo.UserInfoBean" >
<jsp:setProperty name="userInfo" property="*" />
</jsp:useBean>
In this example, the nested
<jsp:setProperty> action sets all properties to
the values of the corresponding parameters when the bean is
created. If the bean already exists, the
<jsp:useBean> action body isn't evaluated. and
the <jsp:setProperty> action isn't executed.
The scope attribute can also be used
with all JSTL actions that expose variables outside their
element bodies to designate where the variable should be
created, as you will see later in this chapter.
You can access a bean created by the
<jsp:useBean> action as a variable in EL
expressions. Typically you just specify the variable name no
matter which scope it's saved in, for instance: <c:out value="${userInfo.userName}" />
In this case, the EL looks for the variable
in all scopes in the order page, request, session, and
application. If it's important to locate a variable in a
specific scope, you can use the implicit variables
representing the different scopes: <c:out value="${pageScope.userInfo.userName}" />
<c:out value="${requestScope.userInfo.userName}" />
<c:out value="${sessionScope.userInfo.userName}" />
<c:out value="${applicationScope.userInfo.userName}" />
Each scope variable represents a collection
(a java.util.Map) of all variables in that scope, so
with expressions like these, the EL looks for the variable
only in the specified scope.
10.1.3 All Together Now
At this point, you have seen the two mechanisms needed
to let multiple pages process the same request: passing
control and passing data. These mechanisms allow you to employ
the MVC design, using one page for request processing and
business logic, and another for presentation. The
<jsp:forward> action can pass control between
the pages, and information placed in the request scope is
available to all pages processing the same request.
Let's apply this to the User Info example. In
Chapter
8, different output was produced depending on whether or
not the user input was valid. If the input was invalid, error
messages were added to inform the user of the problem. Even
when the input was valid, however, the form -- without error
messages, of course -- was displayed.
No more of that. When we split the different
aspects of the application into separate JSP pages as shown in
Figure
10-1, we also change the example so that the form is shown
only when something needs to be corrected. When all input is
valid, a confirmation page is shown instead.
Example
10-1 shows the top part of the userinfoinput.jsp
page.
Example 10-1. Page for
displaying entry form (userinfoinput.jsp) <%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html>
<head>
<title>User Info Entry Form</title>
</head>
<body bgcolor="white">
<jsp:useBean id="userInfo"
scope="request"
class="com.ora.jsp.beans.userinfo.UserInfoBean"
/>
<form action="userinfovalidate.jsp" method="post">
...
The rest of the page is identical to the one
used in Chapter
8. If you compare Example
10-1 with the JSP page used for bean-based validation in
Chapter
8, the only differences are that the userInfo
bean is placed in the request scope (the scope
attribute is set to request), the
<jsp:setProperty> action for capturing input is
gone, and the form's action attribute specifies the
validation page instead of pointing back to the same page.
The validation page, userinfovalidate.jsp, is
given in Example
10-2.
Example 10-2. Input
validation page (userinfovalidate.jsp) <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<jsp:useBean id="userInfo"
scope="request"
class="com.ora.jsp.beans.userinfo.UserInfoBean">
<jsp:setProperty name="userInfo" property="*" />
</jsp:useBean>
<c:choose>
<c:when test="${userInfo.valid}">
<jsp:forward page="userinfovalid.jsp" />
</c:when>
<c:otherwise>
<jsp:forward page="userinfoinput.jsp" />
</c:otherwise>
</c:choose>
This is the request processing page, which
uses the bean to perform the business logic. Note that there's
no HTML at all in this page, only a taglib directive
declaring the core JSTL library and action elements. This is
typical of a request processing page. It doesn't produce a
visible response message, it simply takes care of business and
passes control to the appropriate presentation page.
This example is relatively simple. First, a
new userInfo bean is created in the request scope by
the <jsp:useBean> action, and its properties
are set from the request parameters values submitted from the
form by the nested <jsp:setProperty> action,
just as in Chapter
8. A <c:choose> action element with nested
<c:when> and <c:otherwise>
actions test if the input is valid, using the bean's
valid property. The control is passed to the
appropriate View page depending of the result, using the
<jsp:forward> standard action.
If the input is invalid, the control is
passed back to the userinfoinput.jsp page. This time
the page continues the processing that originated in the
userinfovalidate.jsp page; the
<jsp:useBean> action finds the existing
userInfo bean in the request scope, and its
properties are used to fill out the form fields and add error
messages where needed.
If all input is valid, the control is instead
passed to the userinfovalid.jsp page shown in Example
10-3 to present the "thank you" message.
Example 10-3. Valid input
message page (userinfovalid.jsp) <html>
<head>
<title>User Info Validated</title>
</head>
<body bgcolor="white">
<font color="green" size="+3">
Thanks for entering valid information!
</font>
</body>
</html>
This page tells the user all input was
correct. It consists only of template text, so this could have
been a regular HTML file. Making it a JSP page allows you to
add dynamic content later without changing the referring page,
however. The result of submitting valid input is shown in Figure
10-3.
Let's review how placing the bean in the
request scope lets you access the same bean in all pages. The
user first requests the userinfoinput.jsp page (Example
10-1). A new instance of the userInfo bean is
created in the request scope. Because its properties
have no values, all form fields are empty at this stage. The
user fills out the form and submits it, as a new request, to
the userinfovalidate.jsp (Example
10-2) page. The previous bean is then out of scope, so
this page creates a new userInfo bean in the request
scope and sets all bean properties based on the form field
values. If the input is invalid, the
<jsp:forward> action passes the control back to
the userinfoinput.jsp page. Note that we're still
processing the same request that initially created the bean
and set all the property values. Since the bean is saved in
the request scope, the <jsp:useBean> action
finds it and uses it to generate appropriate error messages and fill out the form with
any values already entered.
|