Process JSPs effectively with JavaBeans

Transport JSP processing logic into a JavaBean with the Template Method design pattern

JavaServer Pages (JSP) technology offers many features for easy and quick development of Web applications. However, if you use those features without some planning and structure, your JSP code can quickly become a mix of various HTML tags, JSP tags, and Java code that is difficult to follow, debug, and maintain.

The objective here is to have JSP code that resembles HTML to the greatest possible degree, by moving all processing code into JavaBeans. The benefit of that approach is that the HTML programmers and graphic designers can do the presentation development (using one of many HTML editors) while you, the Java programmer, can do the programming logic. In addition, the approach makes it easy for you to provide different look and feels for the same Web application.

The framework I'll present here uses the Template Method pattern to enforce common design across the entire application and implement common behavior in each JSP. The common behavior includes, but is not limited to, page state management, common page processing, common error processing, and a mechanism for sharing information between pages. Those are all defined only once, leaving you to deal only with the page-specific details.

I'll present a simple "voting" application as an example of how you can use the framework. You should have basic JSP and Java knowledge, and some UML knowledge is desirable but not mandatory.

Static structure overview

This section gives an overview of the framework core participants, as well the example voting application. Figure 1 shows a UML diagram of the framework's structure:

Figure 1. UML Class Diagram Click on thumbnail to view full-size image.

The framework's central piece consists of two common JSP include files and two classes, described below. Their role is to carry out the common behavior.

  • includeheader.jsp: Include JSP file that must be statically included in the beginning of each JSP.
  • includefooter.jsp: Include JSP file that must be statically included at the end of each JSP.
  • AbstractJSPBean: Abstract class that you should use as a super type for all JSP JavaBean classes. This is the framework's core class.
  • SharedSessionBean: Used to provide associations between all JSP JavaBean objects within one HTTP session.

The purpose of a JSP Webpage is solely presentation. Each JSP must have a corresponding JavaBean that performs the page-specific logic. Each JSP page must statically include includeheader.jsp and includefooter.jsp. Each JavaBean must extend the AbstractJSPBean, which contains the template methods that carry out the common behavior.

The voting application consists of the following JSPs and their corresponding JavaBeans:

  • login.jsp, LoginJSPBean: Authenticates and logs in the voter
  • vote.jsp, VoteJSPBean: Performs the voting
  • confirmation.jsp, ConfirmationJSPBean: Displays confirmation and voting results

I will not examine the classes that emulate database and business logic (Voter, Candidate, and VoteDB) in great detail, but they are required for the example to function properly.

Now that I've covered the top-level view, I'll switch to bottom-up mode by examining one JSP page.

JSP example

Each page must adhere to specific structure in order to comply with the framework.

Listing 1. login.jsp

<%@ page import = "lbm.jsputil.*" %> <jsp:useBean id="_loginJSPBean" class="lbm.examples.LoginJSPBean" scope="session"/> <jsp:setProperty name="_loginJSPBean" property="*"/> <% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %> <%@ include file="includeheader.jsp" %> <html> <head><title>Vote Login</title></head> <body bgcolor="white"> <font size=4> Please enter your Voter ID and Password </font> <font size="3" color="Red"> <jsp:getProperty name="_loginJSPBean" property="errorMsg"/> </font> <font size=3> <form method=post> Voter ID <input type=text name=voterId value=<jsp:getProperty name="_loginJSPBean" property="voterId"/>>

Password <input type=password name=password value=<jsp:getProperty name="_loginJSPBean" property="password"/>>

<input type=submit value="Login"> </form> </font> </body> </html> <%@ include file="includefooter.jsp" %>

The JSP's structure is as follows: The page begins with several JSP statements. The HTML code that follows is free from JSP directives, statements, scriptlets, and so forth. The only exceptions are <jsp:getProperty> directives. You should typically use those directives to retrieve dynamic content from the bean. Finally, the page ends with one JSP include directive.

Let's examine some of those essential JSP statements.

<jsp:useBean id="_loginJSPBean" class="lbm.examples.LoginJSPBean" scope="session"/>
<jsp:setProperty name="_loginJSPBean" property="*"/>

The above code establishes a link between the JSP and the corresponding bean. The second statement implicitly passes values of all form fields (stored as HTTP request parameters) to the matching properties in the bean. The code uses the bean's setter methods. For more information about how that works, check Govind Seshadri's "Advanced form processing using JSP".

<% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %>
<%@ include file="includeheader.jsp" %>

The first statement above will enable the includeheader.jsp to perform the common processing. includeheader.jsp is statically included in the second statement. Note that _loginJSPBean and _abstractJSPBean now refer to the same object, just with different interfaces.

Listing 2. includeheader.jsp

<%-- Set the SharedSessionBean --%>
<jsp:useBean id="_sharedSessionBean" class="lbm.jsputil.SharedSessionBean" scope="session"/>
<% _abstractJSPBean.setSharedSessionBean(_sharedSessionBean); %>
<%-- Set implicit Servlet objects --%>
<% _abstractJSPBean.setRequest(request); %>
<% _abstractJSPBean.setResponse(response); %>
<% _abstractJSPBean.setServlet(this); %>
<%-- Perform the processing associated with the JSP --%>
<% _abstractJSPBean.process(); %>
<%-- If getSkipPageOutput equals false, do not output the JSP page --%>
<% if (! _abstractJSPBean.getSkipPageOutput()) { %>

includeheader.jsp is one of the pattern's core elements. All JSPs use this common element.

The first two statements in Listing 2 enable JSP beans -- from different pages, but within the same HTTP session -- to communicate with each other. Basically, each JSP will have two JavaBeans associated with it: a specific JSP JavaBean (for example, LoginJSPBean) and the common SharedSessionBean. Thus the SharedSessionBean is used as a common element to link all the pages; I'll explain it later in the article.

The next three statements from includeheader.jsp deal with implicit Servlet objects.

<% _abstractJSPBean.setRequest(request); %>
<% _abstractJSPBean.setResponse(response); %>
<% _abstractJSPBean.setServlet(this); %>

The JSP specification provides access to implicit objects that are part of the Java Servlet specification. Such objects as request, response, and servlet are very often useful for page processing. Therefore they are passed to the JSP bean.

<% _abstractJSPBean.process(); %>

Finally, above is the statement that triggers the processing associated with the JSP page. As you can see, you are invoking the method from the abstract JSP bean, not from the concrete LoginJSPBean. Why? I'll explain in the next section.

Apply the Template Method design pattern

AbstractJSPBean is the main participant in the Template Method design pattern. Each concrete JSP JavaBean must extend that class.

Listing 3. AbstractJSPBean.java

package lbm.jsputil;
import java.util.*;
import javax.servlet.http.*;
import javax.servlet.*;
public abstract class AbstractJSPBean {
  /* constants used for _state */
  public static final int NEW = 0;
  public static final int FIRSTPASS = 1;
  public static final int PROC = 2;
  public static final int ERR = -1;
  private int _state; // current state
  private String _errorMsg; // current message that is being appended during validation
  private boolean _skipPageOutput; // should the page output be skipped
  private SharedSessionBean _sharedSessionBean; // used for associating the JSP Bean with the HTTP Session
  /* standard Servlet objects that need to be setup for each JSP Bean */
  protected HttpServletRequest _request;
  protected HttpServletResponse _response;
  protected Servlet _servlet;
  public AbstractJSPBean () {
    setState(NEW);
  }
  protected abstract void beanProcess() throws java.io.IOException;
  protected abstract void beanFirstPassProcess() throws java.io.IOException;
  protected abstract void beanFooterProcess() throws java.io.IOException;
  protected abstract String getJSPCode();
  public void process() throws java.io.IOException {
    setSkipPageOutput(false); // by default do not skip page output. Specific bean process
                              // methods can override it.
    if (getState() == NEW) {
      setState(FIRSTPASS);
      beanFirstPassProcess();
    } else {
      resetErrorMsg();
      setState(PROC);
      beanProcess();
    }
    // validation that all common fields have been properly set by the application
    // this is actually checking that the code has been written properly
    String l_err = "";
    if (_sharedSessionBean == null) l_err = l_err + "; SharedSessionBean must be set";
    if (_request == null) l_err = l_err + "; Request must be set";
    if (_response == null) l_err = l_err + "; Response must be set";
    if (_servlet == null) l_err = l_err + "; Servlet must be set";
    if ( ! l_err.equals("")) throw new IllegalStateException(l_err);
  }
  public void footerProcess() throws java.io.IOException {
    beanFooterProcess();
  }
  protected void addErrorMsg (String addErrorMsg) {
    if (_errorMsg == null) _errorMsg = addErrorMsg;
    else _errorMsg =  _errorMsg + " <br>\n" + addErrorMsg;
    setState(ERR);
  }
  protected void resetErrorMsg () {
    _errorMsg = null;
  }
  public String getErrorMsg () {
    if (_errorMsg == null) return "";
    else return _errorMsg;
  }
  protected void setState (int newState) {
    _state = newState;
  }
  public int getState () {
    return _state;
  }
  public void setSharedSessionBean (SharedSessionBean newSharedSessionBean) {
    if (_sharedSessionBean == null) {
      _sharedSessionBean = newSharedSessionBean;
      _sharedSessionBean.putJSPBean(getJSPCode(), this);
    } else {
      if (_sharedSessionBean != newSharedSessionBean) {
        throw new IllegalStateException("SharedSessionBean is not set properly. SharedSessionBean must be the same for all PageBeans within the session");
      }
    }
  }
  public SharedSessionBean getSharedSessionBean () {
    return _sharedSessionBean;
  }
  public void setSkipPageOutput (boolean newSipPageOutput) {
    _skipPageOutput = newSipPageOutput;
  }
  public boolean getSkipPageOutput () {
    return _skipPageOutput;
  }
  protected void redirect (String redirectURL) throws java.io.IOException {
    // skip the page output since we are redirecting
    setSkipPageOutput(true);
    _response.sendRedirect(redirectURL);
  }
  public void setRequest (HttpServletRequest newRequest) {
    _request = newRequest;
  }
  public void setResponse (HttpServletResponse newResponse) {
    _response = newResponse;
  }
  public void setServlet (Servlet newServlet) {
    _servlet = newServlet;
  }
}

The AbstractJSPBean contains the following abstract methods: beanFirstPassProcess(), beanProcess(), and beanFooterProcess(). Those methods are called the primitive methods. They are the stubs that you must implement in the concrete JSP JavaBeans subclasses. Each one is executed during a particular phase of JSP processing.

  • beanFirstPassProcess() -- Processing that occurs the first time the page is invoked and before the page output has started. It is suitable for initializing the dynamic content and validating access to the page. See how that method is implemented in the VoteJSPBean class to validate the access to the page and control the flow in the application. (See Resources for the source code.)
  • beanProcess() -- Processing that occurs during the second and all subsequent page invocations and before the page output has started. You can use it, for example, for HTML form validations and database updates. See how that method is implemented in the LoginJSPBean class to perform the HTML form processing, and in the VoteJSPBean class to save the information in the database. (See Resources for the source code.)
  • beanFooterProcess() -- Processing that occurs after the page output has completed. You can use it for invalidating the session. See how that method is implemented in the ConfirmationJSPBean class to invalidate the session after the voting has been completed and the confirmation page displayed. (See Resources for the source code.)

Now have a look at the process() method below:

  public void process() throws java.io.IOException {
    setSkipPageOutput(false); // by default do not skip page output. Specific bean process
                              // methods can override it.
    if (getState() == NEW) {
      setState(FIRSTPASS);
      beanFirstPassProcess();
    } else {
      resetErrorMsg();
      setState(PROC);
      beanProcess();
    }
    ....
Related:
1 2 3 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.