Facilitate form processing with the Form Processing API

Use the new servlet-based API with JSPs and JavaBeans to process form data

When designing a solution and then writing code for it, I often face a dilemma: Should I build a quick solution to finish my work quickly, or should I spend more time early on to build a solution that I can reuse in a similar situation? The second temptation won when I started to build the Form Processing API.

Here are four possible steps (and substeps) involved in form processing implementation:

  1. Show the form with all its input elements
    • Generate the presentation code (HTML, XML code)
    • Write special requirements (syntax rules) beside each field, if any, like "this field is required"
    • Display default inputs, if any
  2. Validate the field values that come with the request parameters
    • Parse the request parameters
    • Check whether a value for the requested fields has arrived
    • Validate the text input values against respective syntax rules, if any
    • Validate the text input values against any database query, if any (e.g., you don't want duplicates)
    • Validate the text input values against any other component operating in the background, if any
  3. Reshow the form, in case the validation did not succeed
    • Write the appropriate error messages beside each field that didn't pass validation
    • Display the last user inputs for each field
    • Continue with Step 2 and loop as many times as needed until reaching Step 4
  4. Go on to the next page (if validation went well)

As you can imagine, or have experienced, implementing all these steps is quite a job. In this article, I'll show you how the Form Processing API can help you simplify this process.

Form processing domain analysis

Let's start with an object-oriented analysis of the form processing domain, at the class level. With a bit of OO brainstorming, I came up with the two main classes, FormElement and Form, and one interface, FieldController.

The FormElement class

The FormElement class represents the general concept of all form input elements: text fields, password fields, text areas, checkboxes, radio buttons, combo boxes, and list boxes. In spite of what it specifically represents, a FormElement object (also referred to herein as "field") has four main states (variables):

  • String name identifies the field (FormElement) to the server
  • String value represents a value that the field carries to the server
  • Boolean required represents a flag that shows whether or not an entry for the field is required
  • String errorMessage represents a message that will display a warning, an error message, or nothing beside the field's graphical representation

The first three variables require no further explanation, but the fourth one brings up the question, what would that text message be? With radio buttons, checkboxes, combo boxes, and list boxes, the errorMessage can:

  • State that the user should make at least one choice (in other words, that this field is required)
  • Warn the user that at least one choice should have been made (in the case that this field is required)
  • Show nothing in two cases: when the field is not required, and when the field is required but the user made a selection during the last submission

With text input fields like text fields, text areas, and password fields, the errorMessage can:

  • State that the user must fill this field (in other words, that this field is required)
  • Warn the user that he should have entered something (in the case that this field is required)
  • Warn the user that password entries must be the same (only in the case of password fields)
  • Warn the user that certain characters are prohibited (infinite variation of characters are possible here)
  • Warn the user that certain text entries are prohibited (infinite variation of text entries are possible here)

As you can see, the last two types of warning messages cannot be categorized (or hard-coded in the API), because their variation is infinite. In every infinite situation, FormElement needs some help to generate an errorMessage. Here is where interfaces come into play.

The FieldController interface

Since errorMessages for text input fields vary infinitely, the action for generating them varies infinitely too. But the scenario is always the same: a value is given, and a derived errorMessage is required. It all sounds like a method that takes value as an argument and returns errorMessage, while the implementation of that method is unknown.

FieldController is an interface that only defines one method, getErrorMessage(). This method takes a string value (being validated) as an argument and returns a string errorMessage.

A FormElement object, which represents a text input field, uses an object of type FieldController to implement validation criteria for its value. In this case, the FieldController object is said to be registered with the FormElement. For that purpose, the FormElement class has a method called addFieldController() that takes a FieldController object as an argument. Nothing can go wrong when a FormElement registers more than one FieldController.

The Form class

The Form class represents the concept of a whole set of FormElements. A Form object is responsible for processing a number of FormElement objects that belong to the form. Its process() method validates all field values, and returns "true" when all values are accepted, or "false" otherwise. See Resources for a look at the Form Processing API docs, and more information on Form and other classes.

To stay on track, I won't go deeper into this analysis. The simple UML class diagram below shows all the classes the API includes (in terms of methods, it only contains the most commonly used ones).

Figure 1. UML class diagram of the Form Processing API

All these classes belong to the iostrovica.form package. Check Resources for this package's class files, and the API specification.

A JSP and JavaBeans solution

In the J2EE programming model, JSP pages, supported by servlets, are responsible for delivering dynamic Web content to the client, and the complex business logic tasks should be encapsulated within JavaBeans components (or custom tags). The Form Processing API can be seen as an extension of the Servlet API. Its classes follow the JavaBean standard, giving you the comfort of enjoying all JSP and JavaBean features.

OK, enough with theory, let's apply this to a real-world example. Suppose you have a form that looks like Figure 2:

Figure 2. A registration form

The requirements for each field are as follows:

  • A Login field:
    • It is required
    • It must not contain any of the following three characters: ', ", and &
    • It must contain a minimum four characters, and no spaces
    • It must not duplicate an existing login in a given database
  • A Password field:
    • It is required
    • It must not contain any of the following three characters: ', ", and &
    • It must contain a minimum six characters, and no spaces
    • Its value must be the same as the other password field (the PasswordAgain field)
  • A PasswordAgain field:
    • It has the same requirements as Password
  • A Gender field:
    • It is required
  • A Looking for field
    • It is required
    • The third choice to be shown is selected by default
  • A Province field
    • It is not required
  • A Comments field
    • It is not required
    • It shows "no comment" by default

I will divide the job into four main phases.

Phase 1: Create the FieldController classes

As you may know, the most common requirement in all form fields is the "Is required" requirement; that is why I have included it as a state of the FormElement class. (Note that the PasswordBox class has the required state initially set to true, unlike the rest of the FormElements.)

During this phase I create all FieldController classes needed to satisfy the other requirements of my text input fields. Note that the requirement for password fields, "two password field values must be the same," is handled internally by the Form class. Certainly, if you don't need a FieldController, then just skip this phase.

To impose the character restriction for the Login, Password, and PasswordAgain fields, I have built a CharRestriction class. This class implements the FieldController interface whose getErrorMessage() method will return an error message if the given value contains any of those characters. Yes, I know, some JavaScript code could do the same, but what about the next class called LoginRestriction:

package mypackage.fieldcontroll;
public class LoginRestriction implements iostrovica.form.FieldController {
   public String getErrorMessage(String value) {
      // this is where you would connect to a DataBase
      if (value.equals("tiger") ||
         value.equals("scott") ||
         value.equals("blah") )
         return "sorry, this login already exists";
      return "";
   }
}

In a real application, instead of the if condition, a real database connection will exist, and the code will be completely separated from the form processing code. As you can see, LoginRestriction takes care of the Login field's requirement, "It must not duplicate an existing login in a given database."

Finally, the TextRestriction class will take care of the "Must have minimum x characters, and no spaces" requirement of the Login, Password, and PasswordAgain fields. See Resources for the source code of TextRestriction class.

Phase 2: Extend Form and FormServlet

Form is an abstract class. By extending it and implementing its abstract methods, we will:

  1. Provide a name for the Form bean
  2. Instantiate and initialize all beans that represent the form fields
  3. Instantiate and initialize all necessary FieldControllers
  4. Add the appropriate FieldController to each bean (field)

I will call this class MyForm:

package mypackage;
import iostrovica.form.*;
import mypackage.fieldcontroll.*;
/**
* This is an example of a class that extends Form by providing
* all the data needed to construct a form.
*/
public class MyForm extends Form {
   private CharRestriction charRes;
   private LoginRestriction loginRes;
   private TextRestriction textResForLogin;
   private TextRestriction textResForPassword;
   public MyForm() {
      charRes = new CharRestriction();
      loginRes = new LoginRestriction();
      textResForLogin = new TextRestriction();
      textResForLogin.setCharAtLeast(4);
      textResForLogin.setNoSpace(true);
      textResForPassword = new TextRestriction();
      textResForPassword.setCharAtLeast(6);
      textResForPassword.setNoSpace(true);
   }
         
   public String getFormName() {
      return "thisform";
   }
   public FormElement[] getFormElements() {
      TextBox login = new TextBox();
      login.setName("login");
      login.setRequired(true);
      login.addFieldController(charRes);
      login.addFieldController(loginRes);
      login.addFieldController(textResForLogin);
      PasswordBox password = new PasswordBox();
      password.setName("password");
      password.addFieldController(charRes);
      password.addFieldController(textResForPassword);
      PasswordBox passwordAgain = new PasswordBox();
      passwordAgain.setName("passwordAgain");
      passwordAgain.addFieldController(charRes);
      passwordAgain.addFieldController(textResForPassword);
      RadioButton gender = new RadioButton();
      gender.setName("gender");
      gender.setRequired(true);
      CheckBox lookingFor = new CheckBox();
      lookingFor.setName("lookingFor");
      lookingFor.setRequired(true);
      lookingFor.setValue("fulltime");
      MenuBox province = new MenuBox();
      province.setName("province");
      TextBox comments = new TextBox();
      comments.setName("comments");
      comments.setValue("no comments");
      
      return new FormElement[]{login, password, passwordAgain, gender, lookingFor, province, comments};
   }
}

Note that the getFormName() method provides a name for the Form bean; the getFormElements() method instantiates and initializes all beans that represent the form fields, and adds the appropriate FieldController to each bean (field); and MyForm's empty constructor instantiates and initializes all necessary FieldControllers.

Also note that in the case of fields that are related to multiple values like Gender, Looking for, and Province, you do not have to set their values here. Those values are automatically picked up from this form's HTML presentation. Calling setValue() in those fields is only used to indicate the initially selected values. You can even add or subtract any of these values in the HTML presentation, without needing to touch anything else.

Moving further, FormServlet is an abstract class that I have built as a template for the servlets that handle form processing by creating and using the Form object.

Note that the FormServlet servlet is not part of the Form Processing API; it's a utility class that simplifies the use of the API. This solution (with FormServlet) follows the JSP Model 2 architecture, and is not the only solution. For more information about the JSP Model 2 architecture, check Resources.

This servlet declares three abstract methods that have to be implemented by the extending servlet. See Resources for the source code of the FormServlet servlet.

The MyServlet servlet extends FormServlet and implements its abstract methods:

package mypackage;
import iostrovica.form.*;
/**
* This is an example of a servlet that extends FormServlet by providing
* all the data needed to process a specific form.
* Calling this servlet will display the form page entered in getFormPage() method.
*/
public class MyServlet extends FormServlet{
   public Form getForm() {
      return new MyForm();
   }
   public String getFormPage() {
      return "/jsp/forms/form.jsp";
   }
   public String getWelcomePage() {
      return "/jsp/forms/welcome.jsp";
   }
}
1 2 Page 1
Page 1 of 2