Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Advanced form processing using JSP

Use the Memento design pattern with JavaServer Pages and JavaBeans

Processing HTML forms sounds like a fairly easy task. For simplistic forms, it usually is. However, this "easy task" (as anyone who has written a halfway sophisticated Web application will confirm) can quickly become tedious. This is because handling HTML forms usually involves a lot more than just parsing the request parameters and outputting a response back to the client. 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 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. 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.

Take a look at Listing 2, which 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.

1 | 2 | 3 |  Next >
Resources