WebWork Validation WebWork Validation

by Zarar Siddiqi
01/19/2006

Contents
Setting Up the Sample Application
Manually Validating Forms
   Validating Inside the execute() Method
   Implementing the Validatable Interface
Using Built-In Validators
   Configuring Interceptors
   Specifying Validation Rules
Visitor Field Validation
Displaying Error Messages
Custom Validators
Client-Side Validation
Conclusion
Resources

OpenSymphony's WebWork is a web application framework designed to keep productivity high and the code simple. It has gained popularity for several reasons, including its integration with Spring, a powerful tag library, and OGNL support. Its powerful validation framework is borrowed from another OpenSymphony project, XWork.

In this article, we will explore the various validation features of WebWork/XWork, including custom and conditional validation. A sample application that contains working examples of the different validation techniques is also included. I will also show how to perform "re-usable validation" using WebWork's visitor validation. This article assumes that you are somewhat familiar with WebWork and will focus on the validation aspects of the framework rather than the basics.

Setting Up the Sample Application

You can download the sample application from the Resources section. The sample application uses WebWork 2.2 Beta 3, which comes packaged with XWork 1.0.5. The build.xml file has instructions on how to make the .war file using Apache Ant. The .war file can be deployed in any supporting servlet container. I'm using Apache Tomcat 5.5.12. Copying the .war file into Tomcat's webapps directory should deploy the application, which can then be accessed at the following URL:


http://localhost:8080/webworkapp

If you see a listing of examples by navigating to this page, all is well.

Manually Validating Forms

The straightforward way to validate user input is to write Java code to check the request parameters. WebWork allows you to do this using two different methods.

Validating Inside the execute() Method

The simplest way to validate user input is to verify the request parameters inside of the execute() method of your WebWork actions, as shown in the following example (com...ex1.actions.UserSignupAction).


public String execute() {
  boolean validationPassed = true;
  if (StringUtils.isBlank(user.getName())) {
    addActionError(getText("user.name.empty"));
    validationPassed = false;
  }
  if (StringUtils.isBlank(user.getAddress())) {
    addActionError(getText("user.address.empty"));
    validationPassed = false;
  }
  return (validationPassed ? SUCCESS : INPUT);
}

However, this technique involves putting validation code in the execute() method, which is undesirable as it mixes business logic with validation. A good alternate is to have your action class implement the Validatable interface.

Implementing the Validatable Interface

Instead of validating inside the execute() method, you can have the action class implement the Validatable interface, which requires the implementing class to define a method with the following signature:


public void validate();

As shown below, the validation code for the action resides in validate() instead of execute(), thus separating validation from business logic.



public String execute() {
  return SUCCESS;
}
public void validate() {
  User user = getUser();
  if (StringUtils.isBlank(user.getName())) {
    addActionError(getText("user.name.empty"));
  }
  if (StringUtils.isBlank(user.getAddress())) {
    addActionError(getText("user.address.empty"));
  }
}

Even though the validation code has moved to a much more suitable place, we still find ourselves doing too much work by manually comparing request parameters to determine the validity of our form input. Fortunately, WebWork provides a finely tuned validation framework, which helps developers avoid the chore of writing code for each validation scenario.

Using Built-in Validators

Using built-in validation is a two-step process:

  1. You must tell WebWork the action that you want to validate.
  2. You must tell WebWork what the validation rules are.
Configuring Interceptors

One of the concepts at the foundation of WebWork is that of interceptors. Interceptors allow various tasks to be performed when an action is invoked. Once such task is validation. When we want validation to occur for our actions, we must ensure that WebWork's validator interceptor is in the interceptor stack specified in xwork.xml.

The built-in validationWorkflowStack includes the validator stack and will suffice for our validation needs. Instead of specifying the validationWorkflowStack for each action, I've made it the default for all actions by using the <default-interceptor-ref> element.

However, we don't want to perform validation for all actions (e.g., when simply viewing the form), and for these cases, I've explicitly overridden the validationWorkflowStack with basicStack as shown below:


<action name="viewEx3"
  class="ex3UserSignupAction"
  method="viewInputForm">
  <result name="success">
    /WEB-INF/pages/ex3/input.jsp</result>
  <interceptor-ref name="basicStack"/>
</action>

This might not be ideal in terms of dealing with the workflow of your application, but it serves its purpose in the sample application. You should have a good grip on interceptors before you start working with WebWork.

Tip: In many cases, it is desirable to write a custom interceptor that ignores validation on all GET requests, thus saving the chore of having to specify a basicStack interceptor for each GET action.

Specifying Validation Rules

WebWork provides built-in validators that cover most cases of form validation. They are defined in the web/WEB-INF/classes/validators.xml file and are on Open Symphony's Simple Validators wiki page. You may also specify your own custom validators by adding to this file (more on this later).

The following example validates a user signup (com...ex3.vo.User) using the com...ex3.action.UserSignupAction action. The rules to validate a particular action class are stored in an XML file named after the action, in one of three possible formats:
  1. Based on the Action class: UserSignupAction-validation.xml
  2. Based on an Action alias: UserSignupAction-createUser-validation.xml
  3. Considering the inheritance hierarchy and the interfaces implemented by the Action class.

This example uses the first option. WebWork searches for the rules file in the action's classpath, in this case at com\esolaria\webworkapp\ex3\action\UserSignupAction-validation.xml. The rules file below utilizes some of the built-in validators to validate a User's signup process.


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
  <field name="user.name">
    <field-validator type="requiredstring">
      <message key="user.name.empty">
      resource not found</message>
    </field-validator>
  </field>
  <field name="user.email">
    <field-validator type="requiredstring"
         short-circuit="true">
      <message key="user.email.empty"/>
    </field-validator>
    <field-validator type="email">
      <message key="user.email.invalid"/>
    </field-validator>
  </field>
  <field name="user.phone">
    <field-validator type="stringlength">
      <param name="minLength">10</param>
      <message key="user.phone.length"/>
    </field-validator>
  </field>
  <field name="user.city">
    <field-validator type="requiredstring">
      <message key="user.city.empty"/>
    </field-validator>
  </field>
  <field name="user.favoritePositiveNumber">
    <field-validator type="int">
      <param name="min">1</param>
      <message key="user.favoritePositiveNumber.invalid"/>
    </field-validator>
  </field>
  <field name="user.password">
    <field-validator type="requiredstring">
      <message key="user.password.empty"/>
    </field-validator>
  </field>
  <field name="user.confirmPassword">
    <field-validator type="requiredstring">
      <message key="user.confirmPassword.empty"/>
    </field-validator>
  </field>
    <validator type="expression">
    <param name="expression">
      user.password.equals(user.confirmPassword)</param>
    <message key="user.confirmPassword.nomatch"/>
  </validator>
</validators>

Let's analyze some of the salient aspects of the validation file:

Presence validation: The requiredstring validator forces user input to be a non-empty string. On the other hand, the required validator only forces the user input to be non-null. In most cases, requiredstring is the one to use.

Integer validation: The int validator can be used in two different ways: with min and max ranges and without them. If min and max are specified, the validator forces the input to be an integer between the two specified values. If only one of them is specified, the validator only considers one of the bounds. Validation for the favoritePositiveNumber field is an example of this. The stringlength validator works in a similar way.

Expression validation: One of the most common scenarios in web applications is making the user enter a password twice. In WebWork, comparison between fields when validating is accommodated using the expression validator. In the above example, the values of password and confirmPassword are being compared for equality. Other operators you can use in the expression field include <</code> (&lt;), > (&rt;), startsWith(), and endsWith(), amongst others.

"Short circuiting:" Two validation rules are applied when validating an email: one for existence of data, and one to make sure that the data supplied is a valid email. If the user fails to provide any data for the email, it is pointless to check the input to be of the correct format. The short-circuit attribute of the <field-validator> element prevents any further validation rules being applied if the current one fails. In a scenario where the user leaves the email field blank, the user will receive only one error message.

Validator types: Notice the two different types of validators: <field-validator> and <validator>. Error messages associated with a <field-validator> are stored relative to the field being validated. Errors messages associated with <validator> are stored in an action-level Collection. This is further discussed in the "Displaying Error Messages" section later on in this article.

Internationalization:You can specify a resource key to display the error message for a field. If the resource key does not exist, the contents of the <message> tag will be used. By default, WebWork looks in a file called <ActionClass>.properties in the same package for the resource bundle (in this case, UserSignupAction.properties). Parameters defined in the validator can also be accessed in the resource bundle as shown when defining the error message for the favoritePositiveNumber field.

Visitor Field Validation

It is often the case that the form being validated maps to a business object in our model layer. For example, in the above case, UserSignupAction action is in actuality validating the com...ex3.vo.User object. Suppose now that we want to implement a UserSaveAction where a user can save their already-existing information. The validation rule file, UserSaveAction-validation.xml, would be very similar (if not identical) to UserSignupAction-validation.xml, since the same fields are being validated. To avoid this duplication of code, WebWork has a concept called "visitor field validation," which allows re-use of validation based on business objects.

The following example shows a user signup and a user save scenario using visitor validation. We supply a validation file that has rules to validate properties of a business object. In the example, the business object com...ex4.vo.User is a member variable of both the com...ex4.action.UserSignupAction and com...ex4.action.UserSaveAction. Both will validate against the rules specified in the User's validation file.

The earlier naming convention still applies and the validation file User-validation.xml resides in the same package as User. The User-validation.xml file has the following contents:


<validators>
  <field name="user.firstName">
    <field-validator type="requiredstring">
      <message key="user.firstName.empty"/>
    </field-validator>
  </field>
  <field name="user.lastName">
    <field-validator type="requiredstring">
      <message key="user.lastName.empty"/>
    </field-validator>
  </field>
  <field name="user.email">
    <field-validator type="requiredstring"
       short-circuit="true">
      <message key="user.email.empty"/>
    </field-validator>
    <field-validator type="email"
       short-circuit="true">
      <message key="user.email.invalid"/>
    </field-validator>
  </field>
</validators>

Now that the way to validate a user is defined, all that is left is telling the action classes to invoke the validation on their user member variables. This is done by specifying the following in the actions' validation files, UserSignupAction-validation.xml and UserSaveAction-validation.xml.


<validators>
  <field name="user">
    <field-validator type="visitor">
      <message/>
    </field-validator>
  </field>
</validators>

Input is validated using the User-validation.xml file for both actions.

Displaying Error Messages

WebWork stores validation error messages in two different places, depending on the error. Errors that were added using addActionError() can be retrieved with getActionErrors(), while errors that were added using addFieldError() can be retrieved with getFieldErrors(). When validating fields using XML files, WebWork uses addFieldError() to populate the errors.

When displaying messages, we must check both cases. The method getActionErrors() returns a List of Strings for us to easily loop over. However, getFieldErrors() returns a Map of Lists where the keys of the map correspond to the fields and the values correspond to the error messages associated with the field. In order to display all field-level messages, a nested loop is required.


<ww:if test="hasErrors()">
  <p style="color: red;">
  <b>Errors:</b>
  <ul>
  <ww:if test="hasActionErrors()">
  <ww:iterator value="actionErrors">
    <li style="color: red;"><ww:property/></li>
  </ww:iterator>
  </ww:if>
  <ww:if test="hasFieldErrors()">
  <ww:iterator value="fieldErrors">
    <ww:iterator value="value">
    <li style="color: red;"><ww:property/></li>
    </ww:iterator>
  </ww:iterator>
  </ww:if>
  </ul>
  </p>
</ww:if>

The benefit of adding field-level errors is that it associates errors with an input field, allowing errors to be displayed next to the input field. Using the default theme attribute in the input type displays error messages next to the field. However, in the earlier example, I've set the theme attribute to simple, which does not do this. The disadvantage of using field-specific errors is that if you want to display the messages in a nested for loop (like above), they will not come in order, since a Map's keys are being iterated over.

Action-level errors are stored in a List and will print in the order in which they were added.

Custom Validators

When none of WebWork's built-in validators fit your business needs, you can quite easily declare a custom validator and have it behave the way you want. It can be invoked and used just like the built-in validators.

To create a custom validator, you must define a class that implements the Validator interface, which contains several methods. Fortunately for us, in most cases we don't need to implement all of the methods and instead can extend the ValidatorSupport class, which is an abstract class implementing the Validator interface. All we are left to do is implement the validate(Object) method. Example 5 uses a custom validator to force the user to enter a Canadian postal code if Canada is selected as the country, and an American one if America is the chosen country.

Let's have a look at the validate(Object) method to see how this can be done.


public void validate(Object object)
          throws ValidationException {
  String country = (String)
  getFieldValue("country", object);
  String zipCode = (String)
  getFieldValue("zipCode", object);
  ValidatorContext ctxt = getValidatorContext();
  if ("CAN".equals(country)) {
    if (!Pattern.matches(
      "[a-zA-Z]\\d[a-zA-Z]\\d[a-zA-Z]\\d",
      zipCode)) {
      ctxt.addActionError(
          "Invalid Canadian Zip code (Eg: A1B2C3)");
      return;
    }
  } else if ("US".equals(country)) {
    if (!Pattern.matches("\\d{5}", zipCode)) {
      ctxt.addActionError(
      "Invalid US Zip Code  (Eg: 64145)");
      return;
    }
  }
}

To use your custom validator, you must first register it by adding an entry for it in the validators.xml file.


<validator name="zipcode"
   class="com.esolaria.webworkapp.ex5.ZipCodeValidator"/>

The alias given to the validator is zipcode, which is what we will refer to it by in UserSignupAction-validation.xml file, as shown below:


<field name="country">
  <field-validator type="requiredstring">
    <message>Please select a country</message>
  </field-validator>
</field>
<validator type="zipcode">
  <message/>
</validator>

Client-Side Validation

WebWork provides client-side validation via the DWRValidator class. It uses Direct Web Remoting to validate fields on onblur events. To use client-side validation, the <ww:form> should read:


<ww:form action="submitForm" name="submitForm"
   validate="true">
  <ww:text name="name" required="true"/>
  <ww:submit name="btn" value="Submit" />
</ww:form>

Lack of proper client-side validation is one of the shortcomings of WebWork and will undoubtedly be addressed when it is merged with Struts. Using Direct Web Remoting in this manner has performance and load issues that can be avoided by using simple JavaScript validation. If you want client-side validation, the Jakarta Commons Validator is a good option.

Conclusion

You should now have a good understanding of how to validate web forms using WebWork's validation framework. The authoritative text on WebWork is WebWork in Action, written by its creators Patrick Lightbody and Jason Carreira. It is a valuable resource for anyone working with WebWork and also covers validation in greater detail.

Resources

Zarar Siddiqi is an analyst for the University of Toronto.


 Feed java.net RSS Feeds