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();
    }
    ....

The process() method first examines the JSP’s state; then, depending on the state, it invokes the proper primitive method. It also sets the appropriate state of the JSP.

The process() and footerProcess() methods are called the template methods. They are actually invoked from JSPs (from includeheader.jsp and includefooter.jsp). The concrete beans should not override them. The template methods contain the common skeleton algorithms. A typical template method’s skeleton algorithm performs a common processing and, at certain points, calls the primitive (abstract) methods (beanFirstPassProcess(), beanProcess(), and beanFooterProcess()), whose implementation is deferred to the concrete JSP JavaBeans. The skeleton algorithms may also call the concrete methods that are implemented in the AbstractJSPBean. The above principles represent the essence of the Template Method design pattern.

The benefits of that approach are as follows:

  • You achieve code reuse by factoring out the common behavior in the template method.
  • You enforce the common design and behavior across the entire application.

In addition to the processing logic, the AbstractJSPBean contains the following concrete methods that help the subclasses (concrete JSP JavaBeans) implement their processing tasks. You should not override those concrete methods.

  • Methods related to the User Error management (addErrorMsg(), resetErrorMsg(), and getErrorMsg())
  • Methods related to the Page State management (setState(), getState())
  • Methods that manage associations with the SharedSessionBean
  • Methods that provide the control of whether the HTML part of the JSP page will be outputted or not (setSkipPageOutput(), getSkipPageOutput())
  • The redirect method
  • Accessor methods for Servlet objects: request, response, and servlet

Forms processing, dynamic content, and beans communication

Listing 4 shows one concrete JSP JavaBean, the LoginJSPBean, which implements the page-specific processing.

Listing 4. LoginJSPBean

package lbm.examples;
import lbm.jsputil.*;
import java.util.*;
public class LoginJSPBean extends AbstractJSPBean {
  public static final String PAGE_CODE = "login";
      private String _voterId;
  private String _password;
  private Voter _voter = null;
  public LoginJSPBean() {
  }
  public void setVoterId (String newVoterId) {
    _voterId = newVoterId;
  }
  public String getVoterId() {
      if (_voterId == null) return "";
      else return _voterId;
  }
  public void setPassword (String newPassword) {
            _password = newPassword;
  }
  public String getPassword() {
      if (_password == null) return "";
      else return _password;
  }
  public Voter getVoter () {
    return _voter;
  }
      protected void beanProcess () throws java.io.IOException {
    if (_voterId == null || _voterId.equals("")) {
            addErrorMsg("Voter must be entered");
    }
    if (_password == null || _password.equals("")) {
            addErrorMsg("Password must be entered");
    }
    if (getState() != ERR) {
      //If all the fields are entered, try to login the voter
      Voter voter = VoteDB.login(_voterId, _password);
      if (voter == null) {
            addErrorMsg("Unable to authenticate the Voter. Please try again.");
      }
      else {
        _voter = voter;
        if (_voter.getVotedForCandidate() != null) {
          // if the voter has already voted, send the voter to the last page
          redirect("confirmation.jsp");
        }
        else {
          // go to the Vote page
          redirect("vote.jsp");
        }
      }
    }
  }
  protected void beanFirstPassProcess() throws java.io.IOException {
  }
  protected void beanFooterProcess() throws java.io.IOException {
  }
  protected String getJSPCode() {
    return PAGE_CODE;
  }
}

Observe the set and get methods in the LoginJSPBean class. As previously mentioned, they are used for dynamic matching, and for passing values between the form fields (request parameters) and the bean properties.

The beanProcess() method, shown in Listing 4 above, illustrates some basic form processing. That method is executed during the second and all subsequent page invocations, before the page output has started. That means that it will be executed only after the user presses the Login button and the form action is performed.

You first validate that the mandatory fields voterId and password are entered. Any errors that are encountered are reported by calling the addErrorMsg method. That method sets the errorMsg property of the AbstractJSPBean class. The property is used by the JSP to display the user error:

  <jsp:getProperty name="_loginJSPBean" property="errorMsg"/>

If the data entry validation is successfully passed, the beanProcess() method makes the call to the database to authenticate the user. Finally, it redirects the response to the appropriate page by calling the redirect() method, which is implemented in the AbstractJSPBean class.

Let’s examine a few methods from the VoteJSPBean class. They will illustrate some other aspects of the framework, such as the communication between JSP JavaBeans and the application flow control.

Listing 5. beanFirstPassProcess() method from VoteJSPBean class

  protected void beanFirstPassProcess() throws java.io.IOException {
    // get the Voter from Login page
    _voter = null;
    LoginJSPBean loginJSPBean =
        (LoginJSPBean) getSharedSessionBean().getJSPBean(LoginJSPBean.PAGE_CODE);
    if (loginJSPBean != null) {
      _voter = loginJSPBean.getVoter();
    }
    if (_voter == null) {
      // voter is not logged in yet. Send it to Login page
      setState(NEW);
      redirect("login.jsp");
    }
  }

The method above uses the _sharedSessionBean object from the AbstractJSPBean class. The SharedSessionBean class uses a simple method that enables communications between all JSP JavaBean objects within one HTTP session. It keeps a Map of all JSP JavaBeans within one session. Map is an interface from the Java Collections framework that was introduced with Java 1.2. For those familiar with Java 1.1, it is very similar to Hashtable. The key to a JSP JavaBean is its PAGE_CODE, which is stored as a constant in each JSP JavaBean class.

In that example, the beanFirstPassProcess() method first locates the LoginJSPBean object. Next, it retrieves the Voter object from the LoginJSPBean object and stores the reference to it for later use. If the Voter is null, it means that the user got to the Voter page without logging in first, so it is redirected to the Login page. That is a simple example of controlling the flow in your application. You could devise more complex methods, using a smart dispatcher, for instance; however, that is not within the scope of this article.

Listing 6. getCandidateList() method from VoteJSPBean class

  public String getCandidateList () {
    StringBuffer candidateList = new StringBuffer();
    Candidate candidate;
    Iterator candidates = VoteDB.getCandidates();
    while (candidates.hasNext()) {
      candidate = (Candidate) candidates.next();
      candidateList.append("<input type=radio name="candidateName" value="");
      candidateList.append(candidate.getName());
      candidateList.append(""> ");
      candidateList.append(candidate.getName());
      candidateList.append("<br>n");
    }
    return candidateList.toString();
  }

The getCandidateList() method above is invoked from vote.jsp with the following statement:

  <jsp:getProperty name="_voteJSPBean" property="candidateList"/>

The method actually provides dynamic HTML content based on the data from the database. It does require the Java programmer who develops the JavaBean to have some HTML knowledge.

Alternatively, you could have a separate library with HTML utilities that would format the HTML. The utilities could take a predefined type of input, such as Iterator, and produce the HTML output in one of several predefined formats. Another option is to use the Tag libraries (see Resources).

Final notes: The framework

By separating the presentation from the logic, that framework allows you to modify the presentation (JSPs) and logic (beans) independently. That effectively means that you can modify the logic in the beans without the need to even touch the JSPs, as long as you keep the bean properties (signatures of their accessor methods) unchanged. The same is applicable for the opposite direction. You can take your JSP code and give it to the HTML developers and graphic designers to completely change the site’s look and feel without affecting the Java code.

You can slightly modify the framework’s core elements to suit your particular application needs. You can add new or modify existing helper methods, or modify the template methods. It is important to remember that all your JSPs and JSP JavaBeans should use the framework and be consistent across the application.

The framework may seem complicated in the beginning, and it is definitely too much for the three-page example application. However, when you start writing your application, you will soon notice how things fall nicely into place and how the amount of code you are writing isn’t growing as considerably as your application complexity.

The framework does not address the multitier design that is usually used in Web applications. It is aimed mainly at the JSP presentation tier. To build the truly three-tier or multitier system, JSP JavaBeans would need to call Enterprise JavaBeans or some other business logic implementation.

The example illustrates the framework usage for applications that keep track of HTTP sessions. However, the framework will work perfectly fine if you do not want to rely on the sessions. The application pages and flow would have to be designed differently. You probably would not need the SharedSessionBean. You would probably have the pages that are just for presentation and separate pages that would only perform processing and validation, and would not do any HTML output. For that, you would mainly use the beanFirstPassProcess() method.

Final notes: The example

I deployed and tested the example with Tomcat 3.1. It is compliant with JSP 1.1 and the Servlet 2.2 specification. This article does not cover the particulars of JSP application deployment with Tomcat (check Resources).

Examine the source code of the VoteDB class to obtain the Voter IDs that you can use to log in when testing the example (passwords are the same as the IDs).

You must enable cookies in the browser to successfully run the example. If you require your application to work with cookies disabled, you need to rewrite the URLs (use the encodeURL() method from the javax.servlet.http.HttpServletResponse class). You need to rewrite all the URLs in the application, including links on your JSPs, actions in the form tags, and URLs that are used to redirect the HTTP response in the JSP JavaBeans.

Conclusion

The framework that was presented in this article represents a comprehensive solution for JSP application design. It promotes code reuse, determines the application architecture, and allows for easy extensibility. One of its biggest benefits is that it lets you separate the presentation from the logic and vary each of them independently, without affecting each other.

Milan Adamovic is an independent consultant,
living and working in Vancouver, B.C. His expertise includes system
analysis and design (object-oriented analysis and design, UML,
Oracle CASE, database design), Java server-side programming
(servlets, JSP, EJB), Oracle database, and other programming
languages. His special interests include Internet technologies, Web
application architecture, object-oriented analysis and design, and
software development methodologies.

Source: www.infoworld.com