19.2 JSP Bean Examples
In a JSP-based
application, two types of beans are primarily used: value
beans and utility beans. A value bean encapsulates all information about some
entity, such as a user or a product. A utility bean performs some action, such as saving
information in a database or sending email. Utility beans can
use value beans as input or produce value beans as a result of
an action.
If you develop beans for your application,
you're also preparing for migration to a full-blown J2EE
application. The utility beans can be changed into proxies for
one or more EJB session beans, acting as part of the
Controller for the application.
Value beans may act as what's called Value
Objects in JavaSoft's Designing
Enterprise Applications With the Java 2 Platform, Enterprise
Edition paper, also known as the J2EE Blueprints. In an
EJB-based application, the application's data is represented
by EJB entity beans. Getting a property value from an EJB
entity bean requires a remote call, consuming both system
resources and bandwidth. Instead of making a remote call for
each property value that is needed, the web component can make
one remote call to an EJB session bean (possibly via a JSP
utility bean) that returns all properties of interest packaged
as a value bean. The web component can then get all the
properties from the value bean with inexpensive local calls.
The value bean can also act as cache in the web container to
minimize remote calls even more and can combine information
from multiple EJB entity beans that is meaningful to the web
interface. If you plan to move to the EJB model eventually, I
encourage you to read the J2EE
Blueprint papers (http://java.sun.com/blueprints/enterprise/index.html)
before you design your application to make the migration as
smooth as possible.
19.2.1 Value Beans
Value beans are useful even
without EJB. They are handy for capturing form input, because
the <jsp:setProperty> JSP action automatically
sets all properties with names corresponding to request
parameter names, as described in Chapter
8. In addition, the <jsp:getProperty>
action and the JSTL EL lets you include the property values in
the response without using scripting elements.
Another benefit of value beans is that they
can be used to minimize expensive database accesses for
entities that rarely change their value. By placing a value
bean in the application scope, all users of your application
can use the cached value instead. Example
19-1 shows the source code for the ProductBean
used in Chapter
10 to represent products in an online shopping
application. This is a pure value bean, with only property
accessor methods, that can represent data retrieved from a
database.
Example 19-1. ProductBean
package com.ora.jsp.beans.shopping;
import java.io.*;
public class ProductBean implements Serializable {
private String id;
private String name;
private String descr;
private float price;
public String getId( ) {
return id;
}
public String getName( ) {
return name;
}
public String getDescr( ) {
return descr;
}
public float getPrice( ) {
return price;
}
void setId(String id) {
this.id = id;
}
void setName(String name) {
this.name = name;
}
void setDescr(String descr) {
this.descr = descr;
}
void setPrice(float price) {
this.price = price;
}
}
This bean is created and initialized by the
single instance of the CatalogBean. All setter
methods have package accessibility, while the getter methods
are public. Using package accessibility for the setter methods
ensures that only the CatalogBean can set the
property values. For instance, a JSP page can read the product
information but not change the price.
Another example of a value bean is the
UserInfoBean introduced in Chapter
8. Part of this bean is shown in Example
19-2. Besides encapsulating the property values of the
entity it represents, it also provides methods for validation
of the data.
Example 19-2. Part of the
UserInfoBean package com.ora.jsp.beans.userinfo;
import java.io.*;
import java.util.*;
import com.ora.jsp.util.*;
public class UserInfoBean implements Serializable {
// Validation constants
private static String DATE_FORMAT_PATTERN = "yyyy-MM-dd";
private static String[] GENDER_LIST = {"m", "f"};
private static String[] FOOD_LIST = {"z", "p", "c"};
private static int MIN_LUCKY_NUMBER = 1;
private static int MAX_LUCKY_NUMBER = 100;
// Properties
private String birthDate;
private String emailAddr;
private String[] food;
private String luckyNumber;
private String gender;
private String userName;
public String getBirthDate( ) {
return (birthDate == null ? "" : birthDate);
}
public void setBirthDate(String birthDate) {
this.birthDate = birthDate;
}
public boolean isBirthDateValid( ) {
boolean isValid = false;
if (birthDate != null &&
StringFormat.isValidDate(birthDate, DATE_FORMAT_PATTERN)) {
isValid = true;
}
return isValid;
}
...
In addition to the setter and getter methods
for the birthDate property, the UserInfoBean
includes a separate method for validation. It follows the
naming conventions for a Boolean getter method, so it can be
used in an EL expression to test if the value is valid. The
getter method returns an empty string in case the property is
not set. Without this code, a <jsp:getProperty>
action adds the string null to the response. The JSTL
<c:out> action, on the other hand,
automatically converts a null value to the empty string, so
this type of code is not needed in an application that always
uses <c:out> for adding bean property values to
the response.
All UserInfoBean properties are
represented by this type of getter, setter, and validation
method combo. In addition, the bean includes other validation
and test methods, all of them posing as Boolean read-only
getter methods. They are shown in Example
19-3.
Example 19-3. Validation
and test methods public boolean isValid( ) {
return isBirthDateValid() && isEmailAddrValid( ) &&
isFoodValid() && isLuckyNumberValid( ) &&
isGenderValid() && isUserNameValid( );
}
public boolean isPizzaSelected( ) {
return isFoodTypeSelected("z");
}
public boolean isPastaSelected( ) {
return isFoodTypeSelected("p");
}
public boolean isChineseSelected( ) {
return isFoodTypeSelected("c");
}
private boolean isFoodTypeSelected(String foodType) {
if (food == null) {
return false;
}
boolean selected = false;
for (int i = 0; i < food.length; i++) {
if (food[i].equals(foodType)) {
selected = true;
break;
}
}
return selected;
}
As you may remember from Chapter
8, these read-only properties dramatically simplify the
process of validation and filling out a form with the current
values.
19.2.2 Utility Beans
A utility bean performs some action,
such as processing information, as opposed to simply acting as
a container for information.
The UserInfoBean contains processing
code in addition to the plain property setter and getter
methods, namely the validation and test code. The way the bean
is used in this book, it's perfectly okay to keep the
validation code in the bean itself. However, let's say you
would like to add a property that references another bean, a
friends property for instance, that holds an array of
other UserInfoBean objects. It may then be better to
let a utility bean that knows about all users in the
application perform the validation, including verifying that
the friends exist.
A bean used for validation is one example of
a utility bean you can use to make the application easy to
maintain. The CatalogBean used in Chapter
10 is another example. The version developed for this book
simply creates a set of ProductBean objects with
hardcoded values and provides a method that returns all
products in the catalog. In a real application, it would
likely get the information from a database instead and have
methods for updating catalog information, such as adding and
removing products or changing the information about a product,
as well as methods that return only the products matching
various search criteria. If all catalog update requests go
through the CatalogBean, it can create, delete, and
update the ProductBean objects so that they always
match the information stored in the database. The number of
database accesses can be greatly reduced this way.
Chapter
18 offers another example of how you can use a utility
bean. As opposed to the examples in Chapter
11 and 12, in which the generic JSTL actions are used to
access a database, Chapter
18 uses a bean to encapsulate all database access code.
This strategy gives you an application that's easier to
maintain because modifications due to a possible database
schema change need to be done only in one place. Example
19-4 shows part of the utility bean that handles all
database interactions in Chapter
18.
Example 19-4.
EmployeeRegistryBean package com.ora.jsp.beans.emp;
import java.io.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import javax.sql.*;
import javax.servlet.jsp.jstl.sql.*;
import com.ora.jsp.beans.sql.*;
public class EmployeeRegistryBean implements Serializable {
private DataSource dataSource;
/**
* Sets the dataSource property value.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Returns an EmployeeBean if the specified user name and password
* match an employee in the database, otherwise null.
*/
public EmployeeBean authenticate(String userName, String password)
throws SQLException {
EmployeeBean empInfo = getEmployee(userName);
if (empInfo != null && empInfo.getPassword( ).equals(password)) {
return empInfo;
}
return null;
}
/**
* Returns an EmployeeBean initialized with the information
* found in the database for the specified employee, or null if
* not found.
*/
public EmployeeBean getEmployee(String userName) throws SQLException
// Get the user info from the database
Connection conn = dataSource.getConnection( );
Map empRow = null;
Map[] projects = null;
try {
empRow = getSingleValueProps(userName, conn);
projects = getProjects(userName, conn);
}
finally {
try {
conn.close( );
}
catch (SQLException e) {} // Ignore
}
// Create a EmployeeBean if the user was found
if (empRow == null) {
// Not found
return null;
}
EmployeeBean empInfo = new EmployeeBean( );
empInfo.setDept((String) empRow.get("Dept"));
empInfo.setEmpDate((java.util.Date) empRow.get("EmpDate"));
empInfo.setEmailAddr((String) empRow.get("EmailAddr"));
empInfo.setFirstName((String) empRow.get("FirstName"));
empInfo.setLastName((String) empRow.get("LastName"));
empInfo.setPassword((String) empRow.get("Password"));
empInfo.setUserName((String) empRow.get("UserName"));
empInfo.setProjects(toProjectsArray(projects));
return empInfo;
}
/**
* Inserts the information about the specified employee, or
* updates the information if it's already defined.
*/
public void saveEmployee(EmployeeBean empInfo) throws SQLException {
// Save the user info from the database
Connection conn = dataSource.getConnection( );
conn.setAutoCommit(false);
try {
saveSingleValueProps(empInfo, conn);
saveProjects(empInfo, conn);
conn.commit( );
}
catch (SQLException e) {
conn.rollback( );
}
finally {
try {
conn.setAutoCommit(true);
conn.close( );
}
catch (SQLException e) {} // Ignore
}
}
/**
* Returns a Map with all information about the specified
* employee except the project list, or null if not found.
*/
private Map getSingleValueProps(String userName, Connection conn)
throws SQLException {
if (userName == null) {
return null;
}
SQLCommandBean sqlCommandBean = new SQLCommandBean( );
sqlCommandBean.setConnection(conn);
StringBuffer sql = new StringBuffer( );
sql.append("SELECT * FROM Employee ")
.append("WHERE UserName = ?");
sqlCommandBean.setSqlValue(sql.toString( ));
List values = new ArrayList( );
values.add(userName);
sqlCommandBean.setValues(values);
Result result = sqlCommandBean.executeQuery( );
if (result == null || result.getRowCount( ) == 0) {
// User not found
return null;
}
return result.getRows( )[0];
}
...
The EmployeeRegistryBean has one
property, dataSource, that must be set when the bean
is created. Chapter
18 describes how an application lifecycle listener can
create the bean and initialize it with a DataSource
when the application starts, and then save it in the
application scope where the rest of the application can reach
it. The other public methods in this bean perform the same
function as the generic database actions in Chapters 11 and
12. The getSingleValueProps( ) method, as well other
private methods not shown in Example
19-4, uses an SQLCommandBean to execute the SQL
statement. This bean is included in the source code package
for this book, so you can use it in your own beans as well. We
will look at the implementation in Chapter
23.
A database access utility bean such as the
EmployeeRegistryBean can be used in an application
that combines servlets and JSP. Custom actions developed to
simplify a JSP-only application can also use it. For instance,
the authentication code in the authenticate.jsp file
used in the Chapter
12 example can be reduced with a couple of custom actions
using the EmployeeRegistryBean: ...
<%--
See if the user name and password combination is valid. If not,
redirect back to the login page with a message.
--%>
<myLib:ifUserNotValid user="${param.userName}" pw="${param.password}">
<c:redirect url="login.jsp" >
<c:param name="errorMsg"
value="The User Name or Password you entered is not valid." />
</c:redirect>
</my:ifUserNotValid>
<%--
Create an EmployeeBean and save it in the session scope
--%>
<myLib:createEmployeeBean var="validUser" scope="session"
user="${param.username}" />
The <myLib:ifUserNotValid>
action implementation can use the authenticate( )
method and process its body only if it returns null,
and the <myLib:createEmployeeBean> action can
call the getEmployee( ) method to get an
initialized bean
and save it in the session scope.
19.2.3 Multithreading
Considerations
As you have seen, putting business
logic in beans leads to a more structured and maintainable
application. However, there's one thing you need to be aware
of: beans shared between multiple pages must be thread safe.
Thread safety is an issue for beans only in
the session and application scopes. Beans in the page and
request scope are executed by only one thread at a time. A
bean in the session scope can be executed by more than one
thread initiated by requests from the same client. This may
happen if the user brings up multiple browsers, repeatedly
clicks a submit button in a form, or if the application uses
frames to request multiple JSP pages at the same time. All
application users share application scope beans, so it's very
likely that more than one thread is using an application scope
bean.
Java provides mechanisms for dealing with
concurrent access to resources, such as synchronized blocks
and thread notification methods. But there are other ways to
avoid multithreading issues in the type of beans used in JSP
pages.
Value beans are typically placed in the
request or session scope as containers for information used in
multiple pages. In most cases, they are created and
initialized in one place only, such as by a Controller servlet
or by a <jsp:useBean> and
<jsp:setProperty> combination in the request
processing page invoked by a form, or by a custom action or
utility bean. In all other places, the bean is used only
within EL expressions or by the
<jsp:getProperty> action to read its property
values. Because only one thread writes to the bean and all
others just read it, you don't have to worry about different
threads overwriting each other.
Be aware that if you have a value bean that
can be updated, such as the NewsBean used in Chapter
12, you have to be careful. The NewsBean contains
an instance variable that holds a list of
NewsItemBean objects and has methods for retrieving,
adding, and removing news items. If one thread calls
removeNewsItem( ) while another is executing
getNewsItems( ), a runtime exception may occur. Example
19-5 shows how to use synchronization to guard against
this problem.
Example 19-5. Synchronized
access to instance variable package com.ora.jsp.beans.news;
import java.io.*;
import java.util.*;
import com.ora.jsp.util.*;
public class NewsBean implements Serializable {
private ArrayList newsItems = new ArrayList( );
private int[] idSequence = new int[1];
...
public NewsItemBean[] getNewsItems( ) {
NewsItemBean[] a = null;
synchronized (newsItems) {
a = (NewsItemBean[])
newsItems.toArray(new NewsItemBean[newsItems.size( )]);
}
return a;
}
public void setNewsItem(NewsItemBean newsItem) {
synchronized (idSequence) {
newsItem.setId(idSequence[0]++);
}
synchronized (newsItems) {
newsItems.add(newsItem);
}
}
public void removeNewsItem(int id) {
synchronized (newsItems) {
for (int i = 0; i < newsItems.size( ); i++) {
NewsItemBean item = (NewsItemBean) newsItems.get(i);
if (id == item.getId( )) {
newsItems.remove(i);
break;
}
}
}
}
...
}
The java.util.ArrayList used to hold
the news items is not thread-safe, meaning it does not provide
any synchronization on its own. All public NewsBean
methods that read or modify the list must therefore
synchronize on the newsItems object. The effect is
that while one thread is manipulating the list of news items
through one of these methods, all other threads wait until the
current thread leaves the synchronized block. I could have
used a java.util.Vector instead of the
ArrayList in this bean; it's a class that
synchronizes all access to its elements, so the bean would not
have had to. In many cases, you want to use unsynchronized
access when you're sure only one thread has access to the list
to gain performance, and then the ArrayList is a
better choice. In the NewsBean, for instance, the
list is filled with existing news items in the bean's
constructor; only one thread can run the constructor, so it's
safe to add the items without synchronization.
The setNewsItem( ) method also
synchronizes on idSequence, a variable that generates
a unique ID for each item. idSequence is an
int array with one component. This is a neat trick
for synchronized access to an integer value; Java doesn't
support synchronization on primitive types, only on objects,
but an array of a primitive type is an object. You can use an
Integer object instead, but you can't change the
value of an Integer. To increment the value, a new
Integer must be created. Using an array avoids these
repeated object creations (and creating an object is a fairly
expensive operation in Java).
Another approach that avoids multithreading
problems is used in the EmployeeRegistryBean
described in the previous section. It defines setter methods
only for customization that takes place when the bean is
created and defines all data needed to perform a task as
method parameters instead of properties. Each thread has its
own copy of method parameter values and local variables, so
with this approach, there's no risk that one thread will step
on another.
|