Server-side Java: Advanced form processing using JSP

Use the Memento design pattern with JavaServer Pages and JavaBeans

Typically, form processing involves multiple components operating in the background, with each component responsible for a discrete task such as state management, data validation, database access, and so on. While there are numerous examples that demonstrate form processing with Perl scripts and servlets, using JSPs for this purpose has received little attention. There is a reason for this. Apart from the fact that JSP is a fairly new technology, many view it as being suitable mostly for handling the presentation of dynamic content sourced from either JavaBeans or servlets. However, as you shall soon see, the combination of JSP with JavaBeans can be a force to reckon with when processing HTML forms.

In this article, I will examine the handling of a user registration form using JSP. One of the basic programming tenets of JSP is to delegate as much processing as possible to JavaBean components. My JSP form-handling implementation will demonstrate some interesting features. It will not only provide basic data validation for the registration information input by a user, but will also exhibit stateful behavior. This lets you pre-fill the form’s input elements with validated data as the user loops through the submission cycle and finally enters the correct data for all of the input elements. So, without further ado, let’s dive into the example.

Take a look at Listing 1, which presents the user with a simple registration form, displayed in Figure 1.

Listing 1. register.html

<html>
<body>
<form action="/examples/jsp/forms/process.jsp" method=post>
<center>
<table class="legacyTable" cellpadding=4 cellspacing=2 border=0>
<th bgcolor="#CCCCFF" colspan=2>
<font size=5>USER REGISTRATION</font>
<br>
<font size=1><sup>*</sup> Required Fields</font>
</th>
<tr bgcolor="#c8d8f8">
<td valign=top> 
<b>First Name<sup>*</sup></b> 
<br>
<input type="text" name="firstName" value="" size=15 maxlength=20></td>
<td  valign=top>
<b>Last Name<sup>*</sup></b>
<br>
<input type="text" name="lastName" value="" size=15 maxlength=20></td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top>
<b>E-Mail<sup>*</sup></b> 
<br>
<input type="text" name="email" value="" size=25  maxlength=125>
<br></td>
<td  valign=top>
<b>Zip Code<sup>*</sup></b> 
<br>
<input type="text" name="zip" value="" size=5  maxlength=5></td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top colspan=2>
<b>User Name<sup>*</sup></b>
<br>
<input type="text" name="userName" size=10 value=""  maxlength=10>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top>
<b>Password<sup>*</sup></b> 
<br>
<input type="password" name="password1" size=10 value=""  
maxlength=10></td>
<td  valign=top>
<b>Confirm Password<sup>*</sup></b>
<br>
<input type="password" name="password2" size=10 value=""  
maxlength=10></td>
<br>
</tr>
<tr bgcolor="#c8d8f8">
<td  valign=top colspan=2>
<b>What music are you interested in?</b>
<br>
<input type="checkbox" name="faveMusic" 
value="Rock">Rock    
<input type="checkbox" name="faveMusic" value="Pop">Pop  
<input type="checkbox" name="faveMusic" value="Bluegrass">Bluegrass<br>
<input type="checkbox" name="faveMusic" value="Blues">Blues  
<input type="checkbox" name="faveMusic" value="Jazz">Jazz  
<input type="checkbox" name="faveMusic" value="Country">Country<br>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td  valign=top colspan=2>
<b>Would you like to receive e-mail notifications on our special 
sales?</b>
<br>
<input type="radio" name="notify" value="Yes" checked>Yes 
      
<input type="radio" name="notify" value="No" > No 
<br><br></td>
</tr>
<tr bgcolor="#c8d8f8">
<td  align=center colspan=2>
<input type="submit" value="Submit"> <input type="reset"  
value="Reset">
</td>
</tr>
</table>
</center>
</form>
</body>
</html>

Figure 1 shows the user registration form as it would appear in a browser.

Figure 1. The user registration form

There should be nothing unusual about the form shown in Listing 1. It contains all of the commonly used input elements, including text-entry fields, checkboxes, and radio buttons. However, notice the action clause of the form:

<form action="/examples/jsp/forms/process.jsp" method=post>

Although typically you may have specified a servlet or Perl script, note that a JSP is perfectly capable of processing data posted from an HTML form. This should not surprise you, since after all, what is JSP? It is nothing but a high-level abstraction of servlets. Thus, in most cases it is entirely feasible to write a JSP equivalent to a servlet.

Still, you should always remember that JSP technology was created for an entirely different purpose than serving as an alternate (some would say easier!) mechanism for creating servlets. JSP is all about facilitating the separation of presentation from dynamic content. Although you can embed any amount of Java code within a JSP page, your best bet is to encapsulate the processing logic within reusable JavaBean components. Nevertheless, in my opinion, it should also be perfectly appropriate to develop controller JSP pages. These pages would still delegate the bulk of the processing to component beans, but they would also contain some conditional logic to respond to a user’s actions. But these controller pages would never contain presentation logic to display UI elements. This task would always be externalized into separate JSPs, which will be invoked as needed by the controller.

Listing 2 demonstrates a JSP serving as a controller.

Listing 2. process.jsp

<%@ page import="java.util.*" %>
<%! 
    ResourceBundle bundle =null;
    public void jspInit() {
       bundle = ResourceBundle.getBundle("forms");
      }
%>
<jsp:useBean id="formHandler" class="foo.FormBean" scope="request">
<jsp:setProperty name="formHandler" property="*"/>
</jsp:useBean>
<% 
   if (formHandler.validate()) {
%>
    <jsp:forward page="<%=bundle.getString("process.success")%>"/>
<%
   }  else {
%>
    <jsp:forward page="<%=bundle.getString("process.retry")%>"/>
<%
   }
%>

Because we are delegating the bulk of the processing to JavaBeans, the first thing the controller has to do is instantiate the bean component. This is done with the <jsp:useBean> tag as follows:

<jsp:useBean id="formHandler" class="foo.FormBean" scope="request">
    <jsp:setProperty name="formHandler" property="*"/>
</jsp:useBean>

The <jsp:useBean> tag first looks for the bean instance with the specified name, and instantiates a new one only if it cannot find the bean instance within the specified scope. Here, the scope attribute specifies the lifetime of the bean. Newly instantiated beans have page scope by default, if nothing is specified. Observe that in this case, I specify that the bean have request scope before a response is sent back to the client, since more than one JSP is involved in processing the client request.

You may be wondering about the <jsp:setProperty> within the body of the <jsp:useBean> tag. Any scriptlet or <jsp:setProperty> tags present within the body of a <jsp:useBean> tag are executed only when the bean is instantiated, and are used to initialize the bean’s properties. Of course, in this case I could have placed the <jsp:setProperty> tag on the outside of the <jsp:useBean>‘s body. The difference between the two is that the contents of the body are not executed if the bean is retrieved from the specified scope — which is moot in this case since the bean is instantiated each time the controller is invoked.

Introspective magic

When developing beans for processing form data, you can follow a common design pattern by matching the names of the bean properties with the names of the form input elements. You would also need to define the corresponding getter/setter methods for each property within the bean. For example, within the FormBean bean (shown in Listing 3), I define the property firstName, as well as the accessor methods getFirstName() and setFirstName(), corresponding to the form input element named firstName. The advantage in this is that you can now direct the JSP engine to parse all the incoming values from the HTML form elements that are part of the request object, then assign them to their corresponding bean properties with a single statement, like this:

  <jsp:setProperty name="formHandler" property="*"/>

This runtime magic is possible through a process called introspection, which lets a class expose its properties on request. The introspection is managed by the JSP engine, and implemented via the Java reflection mechanism. This feature alone can be a lifesaver when processing complex forms containing a significant number of input elements.

Listing 3. FormBean.java

package foo;
import java.util.*;
public class FormBean {
  private String firstName;
  private String lastName;
  private String email;
  private String userName;
  private String password1;
  private String password2;
  private String zip;
  private String[] faveMusic;
  private String notify;
  private Hashtable errors;
  public boolean validate() {
    boolean allOk=true;
    if (firstName.equals("")) {
      errors.put("firstName","Please enter your first name");
      firstName="";
      allOk=false;
    }
    if (lastName.equals("")) {
      errors.put("lastName","Please enter your last name");
      lastName="";
      allOk=false;
    }
    if (email.equals("") || (email.indexOf('@') == -1)) {
      errors.put("email","Please enter a valid email address");
      email="";
      allOk=false;
    }
    if (userName.equals("")) {
      errors.put("userName","Please enter a username");
      userName="";
      allOk=false;
    }
    if (password1.equals("") ) {
      errors.put("password1","Please enter a valid password");
      password1="";
      allOk=false;
    }
    if (!password1.equals("") && (password2.equals("") || 
        !password1.equals(password2))) {
      errors.put("password2","Please confirm your password");
      password2="";
      allOk=false;
    }
    if (zip.equals("") || zip.length() !=5 ) {
      errors.put("zip","Please enter a valid zip code");
      zip="";
      allOk=false;
    } else {
      try {
        int x = Integer.parseInt(zip);
      } catch (NumberFormatException e) {
        errors.put("zip","Please enter a valid zip code");
        zip="";
        allOk=false;
      }
    }
    return allOk;
  }
  public String getErrorMsg(String s) {
    String errorMsg =(String)errors.get(s.trim());
    return (errorMsg == null) ? "":errorMsg;
  }
  public FormBean() {
    firstName="";
    lastName="";
    email="";
    userName="";
    password1="";
    password2="";
    zip="";
    faveMusic = new String[] { "1" };
    notify="";
    errors = new Hashtable();
  }
  public String getFirstName() {
    return firstName;
  }
  public String getLastName() {
    return lastName;
  }
  public String getEmail() {
    return email;
  }
  public String getUserName() {
    return userName;
  }
  public String getPassword1() {
    return password1;
  }
  public String getPassword2() {
    return password2;
  }
  public String getZip() {
    return zip;
  }
  public String getNotify() {
    return notify;
  }
  public String[] getFaveMusic() {
    return faveMusic;
  }
  public String isCbSelected(String s) {
    boolean found=false;
    if (!faveMusic[0].equals("1")) {
      for (int i = 0; i < faveMusic.length; i++) {
        if (faveMusic[i].equals(s)) {
          found=true;  
          break;
        }
      }
      if (found) return "checked";
    } 
    return "";
  }
  public String isRbSelected(String s) {
    return (notify.equals(s))? "checked" : "";
  }
  public void setFirstName(String fname) {
    firstName =fname;
  }
  public void setLastName(String lname) {
    lastName =lname;
  }
  public void setEmail(String eml) {
    email=eml;
  }
  public void setUserName(String u) {
    userName=u;
  }
  public void  setPassword1(String p1) {
    password1=p1;
  }
  public void  setPassword2(String p2) {
    password2=p2;
  }
  public void setZip(String z) {
    zip=z;
  }
  public void setFaveMusic(String[] music) {
    faveMusic=music;
  }
  public void setErrors(String key, String msg) {
    errors.put(key,msg);
  }
  public void setNotify(String n) {
    notify=n;
  }
}

It is not mandatory that the bean property names always match those of the form input elements, since you can always perform the mapping yourself, like this:

  <jsp:setProperty name="formHandler" param="formElementName" 
    property="beanPropName"/>

While you can easily map form input types like text-entry fields and radio buttons to a scalar bean property type like String, you have to use an indexed property type like String[] for handling checkboxes. Nevertheless, as long as you provide a suitable setter method you can still have the JSP engine assign values even for non-scalar types by automatically parsing the corresponding form element values from the request object.

Observe the setter method for setting the values of selected checkboxes:

public void setFaveMusic(String[] music) {
    faveMusic=music;
}

I’ve also shown a simple technique to avoid hardcoding the target resources within the controller. Here, the targets are stored within an external properties file, forms.properties, as shown in Listing 4. This file may be located anywhere in the CLASSPATH that is visible to the JSP container. The jspInit() method declared within the page is automatically executed by the JSP container when the JSP page (or to be more accurate, the servlet class representing the page) is loaded into memory, and invoked just once during the JSP’s lifetime. By making use of the ResourceBundle facility, your page can access the values for the target resources by name, just like you would with a Hashtable object.

Listing 4. forms.properties

process.success=success.jsp
process.retry=retry.jsp

Form handling using the Memento pattern

The processing logic within the controller page is straightforward. After the bean is instantiated, its validate() method is invoked. The validate() method has a two-pronged effect. If an error is encountered during the validation of any form input element, the validate() method not only resets the value of the corresponding bean property, but also sets an appropriate error message, which can later be displayed for that input element.

If any of the required form elements cannot be successfully validated, the controller forwards the request to the JSP page retry.jsp (shown in Listing 5), allowing the user to make changes and resubmit the form. If there are no validation errors, the request is forwarded to success.jsp, shown in Listing 6.

Within retry.jsp, you first obtain a reference to the bean component that was previously instantiated and placed into the request by the controller. Also, since you don’t want the user to reenter previously validated data, you refill the form elements by interrogating their previous state from the bean, like this:

<input type="text" name="firstName"  
   value="<%=formHandler.getFirstName()%>" size=15 maxlength=20>

Any error message that may be applicable for the form input element is also retrieved and displayed via:

<font size=2 color=red>
  <%=formHandler.getErrorMsg("firstName")%>
</font>

Also, observe the way I recreate the prior state of form input elements like radio buttons and checkboxes by using utility methods such as isRbSelected(String s) and isCbSelected(String s), which were incorporated into the bean. Figure 2 shows the form generated by retry.jsp, which indicates some validation errors.

Listing 5. retry.jsp

<jsp:useBean id="formHandler" class="foo.FormBean" scope="request"/>
<html> 
<body>
<form action="process.jsp" method=post>
<center>
<table class="legacyTable" cellpadding=4 cellspacing=2 border=0>
<th bgcolor="#CCCCFF" colspan=2>
<font size=5>USER REGISTRATION</font>
<br>
<font size=1><sup>*</sup> Required Fields </font>
</th>
<tr bgcolor="#c8d8f8">
<td valign=top> 
<B>First Name<sup>*</sup></B> 
<br>
<input type="text" name="firstName" 
value="<%=formHandler.getFirstName()%>" size=15 maxlength=20>
<br><font size=2 
color=red><%=formHandler.getErrorMsg("firstName")%></font>
</td>
<td  valign=top>
<B>Last Name<sup>*</sup></B>
<br>
<input type="text" name="lastName" 
value="<%=formHandler.getLastName()%>" size=15 maxlength=20>
<br><font size=2 
color=red><%=formHandler.getErrorMsg("lastName")%></font>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top>
<B>E-Mail<sup>*</sup></B> 
<br>
<input type="text" name="email" value="<%=formHandler.getEmail()%>" 
size=25  maxlength=125>
<br><font size=2 color=red><%=formHandler.getErrorMsg("email")%></font>
</td>
<td  valign=top>
<B>Zip Code<sup>*</sup></B> 
<br>
<input type="text" name="zip" value="<%=formHandler.getZip()%>" size=5  
maxlength=5>
<br><font size=2 color=red><%=formHandler.getErrorMsg("zip")%></font>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top colspan=2> 
<B>User Name<sup>*</sup></B>
<br>
<input type="text" name="userName" size=10 
value="<%=formHandler.getUserName()%>"  maxlength=10>
<br><font size=2 
color=red><%=formHandler.getErrorMsg("userName")%></font>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td valign=top>
<B>Password<sup>*</sup></B> 
<br>
<input type="password" name="password1" size=10 
value="<%=formHandler.getPassword1()%>"  maxlength=10>
<br><font size=2 
color=red><%=formHandler.getErrorMsg("password1")%></font>
</td>
<td  valign=top>
<B>Confirm Password<sup>*</sup></B>
<br>
<input type="password" name="password2" size=10 
value="<%=formHandler.getPassword2()%>"  maxlength=10>
<br><font size=2 
color=red><%=formHandler.getErrorMsg("password2")%></font>
</td>
<br>
</tr>
<tr bgcolor="#c8d8f8">
<td colspan=2 valign=top>
<B>What music are you interested in?</B>
<br>
<input type="checkbox" name="faveMusic" value="Rock" 
<%=formHandler.isCbSelected("Rock")%>>Rock    
<input type="checkbox" name="faveMusic" value="Pop" 
<%=formHandler.isCbSelected("Pop")%>>Pop  
<input type="checkbox" name="faveMusic" value="Bluegrass" 
<%=formHandler.isCbSelected("Bluegrass")%>>Bluegrass<br>
<input type="checkbox" name="faveMusic" value="Blues" 
<%=formHandler.isCbSelected("Blues")%>>Blues  
<input type="checkbox" name="faveMusic" value="Jazz" 
<%=formHandler.isCbSelected("Jazz")%>>Jazz  
<input type="checkbox" name="faveMusic" value="Country" 
<%=formHandler.isCbSelected("Country")%>>Country<br>
</td>
</tr>
<tr bgcolor="#c8d8f8">
<td colspan=2 valign=top>
<B>Would you like to receive e-mail notifications on our special 
sales?</B>
<br>
<input type="radio" name="notify" value="Yes" 
<%=formHandler.isRbSelected("Yes")%>>Yes       
<input type="radio" name="notify" value="No" 
<%=formHandler.isRbSelected("No")%>> No 
<br><br></td>
</tr>
<tr bgcolor="#c8d8f8">
<td colspan=2 align=center>
<input type="submit" value="Submit"> <input type="reset"  
value="Reset">
</td>
</tr>
</table>
</center>
</form>
</body>
</html>
Figure 2. Form generated by retry.jsp

Because retry.jsp also posts data to process.jsp, the controller repeatedly instantiates the bean component and validates the form data until the user has entered valid data for all of the form elements.

Using a bean within a form in this way can be viewed as an implementation of the Memento design pattern. Memento is a behavioral pattern whose intent is to take a snapshot of a portion of an object’s state so that the object can be restored to that state later, without violating its encapsulation. Because the bean is created and accessed within the boundary of the same request, the encapsulation of the memento is preserved intact.

As stated earlier, the controller forwards the request to success.jsp only after all of the submitted form data has been successfully validated. Success.jsp in turn extracts the bean component from the request and confirms the registration to the client. Note that while the scalar bean properties can be retrieved using a JSP expression or the <jsp:getProperty> tag, you still have to jump through a few hoops in order to display the indexed property type:

<%
  String[] faveMusic = formHandler.getFaveMusic();
  if (!faveMusic[0].equals("1")) {
    out.println("<ul>");
    for (int i=0; i<faveMusic.length; i++)  
      out.println("<li>"+faveMusic[i]);
    out.println("</ul>");
  } else out.println("Nothing was selected");
%>

In the future, you should not have to use scriptlet code to access indexed properties, but would instead use custom tags that support looping. Figure 3 shows the client on completion of a successful registration.

Listing 6. success.jsp




USER REGISTRATION SUCCESSFUL!
First Name Last Name
E-Mail Zip Code
User Name
What music are you interested in? <% String[] faveMusic = formHandler.getFaveMusic(); if (!faveMusic[0].equals("1")) { out.println("<ul>"); for (int i=0; i<faveMusic.length; i++) out.println("<li>"+faveMusic[i]); out.println("</ul>"); } else out.println("Nothing was selected"); %>
Would you like to receive e-mail notifications on our special sales?
Figure 3. Confirmation of a successful registration

Request chaining with JSPs and servlets

Although you saw the bean perform extensive state management and form validation, you may have noticed that the only validation performed on the userName element was to simply confirm that it was not blank! Typically, when adding new users to a database, your registration program has to first ensure that the username is unique and was not previously entered into the database. If you already have a database access servlet for this purpose, you may wonder how you can integrate it into the validation scenario discussed thus far. Well, the answer is, “Very easily!” You can simply forward the request to a servlet, just as you would to a JSP page, and continue processing. The servlet in turn can update the bean (or even add new beans to the request) and forward the request to another resource down the chain.

For example, you can make a minor change to the controller, process.jsp, so that it now forwards the request to a servlet on successful validation, instead of sending it to the success.jsp page as it did before:

<% if (formHandler.validate()) {
%>
  <jsp:forward page="/servlet/DBHandler"/>
<%
   }  else {
      // continue as before
   }
%>

Consider the servlet DBHandler shown in Listing 7.

Listing 7. A database access servlet

import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import foo.FormBean;
public class DBHandler extends HttpServlet {
  public void doPost (HttpServletRequest request, HttpServletResponse response) {
    try {
      FormBean f = (FormBean) request.getAttribute("formHandler"); 
      boolean userExists = false;
      //obtain a db connection and perform a db query 
      //ensuring that the username does not exist
      //set userExists=true if user is found in db
      //for a simple test, you can disallow the registration
      //of the username "rogerm" as:
      //if (f.getUserName().equals("rogerm")) userExists=true;
      if (userExists) {
        f.setErrors("userName","Duplicate User: Try a different username"); 
        getServletConfig().getServletContext().
          getRequestDispatcher("/jsp/forms/retry.jsp").
          forward(request, response);
      } else {
        //retrieve the bean properties and store them
        // into the database. 
        getServletConfig().getServletContext().
          getRequestDispatcher("/jsp/forms/success.jsp").
          forward(request, response);
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

Since the bean is still present within the request scope, you can easily access it within the servlet, like this:

FormBean f = (FormBean) request.getAttribute ("formHandler");

Assuming that the servlet performs a database query and finds another user with the same username, the servlet can easily call a setter method within the bean to indicate the error condition, like this:

f.setErrors("userName","Duplicate User: Try a different username");

Once again, the servlet can then send the request along its merry way by means of a request dispatcher:

getServletConfig().getServletContext().
    getRequestDispatcher("/jsp/forms/retry.jsp").
    forward(request, response);

Figure 4 shows a situation in which the servlet, DBHandler, was able to locate a duplicate username.

Figure 4. Form displaying the duplicate user error generated by the servlet

If the servlet does not find a duplicate user within the database, it can update the database after accessing the registration information from the bean, and then forward the request to the success.jsp page confirming a successful registration.

Deploying the application

I will assume that you are using Sun’s latest version of JavaServer Web Development Kit (JSWDK) to run the example. If you aren’t, see the Resources section to find out where to get it. Assuming that the server is installed in jswdk-1.0.1, its default location under Microsoft Windows, deploy the application files as follows:

  • Copy register.htm to jswdk-1.0.1webpages
  • Create the directory “forms” under jswdk-1.0.1examplesjsp
  • Copy process.jsp to jswdk-1.0.1examplesjspforms
  • Copy retry.jsp to jswdk-1.0.1examplesjspforms
  • Copy success.jsp to jswdk-1.0.1examplesjspforms
  • Copy forms.properties to jswdk-1.0.1examplesWEB-INFjspbeans
  • Copy FormBean.java to jswdk-1.0.1examplesWEB-INFjspbeans
  • Compile FormBean.java by typing javac -d . FormBean.java

This should create jswdk-1.0.1examplesWEB-INFjspbeansfooFormBean.class. If you are also testing the servlet, you will need to:

  • Update process.jsp to forward the request to the servlet as shown earlier
  • Update the classpath to include jswdk-1.0.1examplesWEB-INFjspbeans
  • Copy DBHandler.java to jswdk-1.0.1examplesWEB-INFservlets
  • Compile DBHandler.java by typing javac DBHandler.java

Once your server has been started, you should be able to access the application using as the URL.

Conclusion

While there are numerous established solutions for handling HTML forms, there are many compelling reasons why JSP makes a viable alternative to the more mainstream solutions. JSP, with its component bean-centric approach, may actually ease the processing of complex forms. The JSP container can also reduce the processing burden significantly by instantiating bean components and automatically parsing the request object. Following a bean-centric approach also makes it easier to implement design patterns like Memento, which can play a useful role in the form validation process. Using JSP for form handling does not preclude the use of servlets, as these complementary technologies can be effectively melded using techniques like request chaining.

Govind Seshadri is an Enterprise Java Guru for
jGuru.com, and the author of
Enterprise Java Computing — Applications and Architecture
from Cambridge University Press (1999). Learn more about Govind at jGuru.com.

Source: www.infoworld.com