Book: JavaServer Pages™, 2nd Edition
Section: Chapter 19.  Developing JavaBeans Components for JSP



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.

    [http://safari.oreilly.com/059600317X/jserverpages2-CHP-19-SECT-2]


    Copyright © 2002 O'Reilly & Associates, Inc. All rights reserved.
    1005 Gravenstein Highway North
    Sebastopol, CA 95472