Skip to content

5. Dynamic Forms with Integrity Constraints

We will now develop a new application called strutspersonne2 using

  • a dynamic form like in strutspersonne1
  • a file declaring the integrity constraints to be checked by the fields of this dynamic form

5.1. Declaration of integrity constraints

The class used to store the name and age values in the strutspersonne1 application was declared as follows:

    <form-beans>
        <form-bean name="frmPersonne" type="istia.st.struts.personne.PersonneDynaForm">
            <form-property name="name" type="java.lang.String" initial=""/>
            <form-property name="age" type="java.lang.String" initial=""/>
        </form-bean>            
    </form-beans>

We had to write the PersonneDynaForm class to provide a validate method capable of verifying that the name and age values in the dynamic form were valid. There were two types of checks to perform:

  • both fields must not be empty
  • the age field had to match the mask (regular expression) \s*\d+\s*

These two validations are among those that the StrutsValidator environment can perform. This environment comes with Struts and includes a number of classes found in commons-validator.jar and jakarta-oro.jar. If you followed the procedure, explained at the beginning of this document, for installing the Struts libraries within Tomcat, these libraries are already available in Tomcat. Make sure they are also available in JBuilder. This was also explained at the beginning of this document.

The new form declaration in struts-config.xml becomes the following:

    <form-beans>
        <form-bean name="frmPersonne" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="nom" type="java.lang.String" initial=""/>
            <form-property name="age" type="java.lang.String" initial=""/>
        </form-bean>            
    </form-beans>

There is therefore very little difference. The class associated with the dynamic form is now a predefined StrutsValidator class: org.apache.struts.validator.DynaValidatorForm. The developer no longer needs to write a class. Instead, they specify the integrity constraints that the form must check in a separate XML file. The Struts controller must know the name of this file. To achieve this, a new configuration section appears in the struts-config.xml file:

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property 
        property="pathnames" 
      value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"
    />
    </plug-in>

The <plug-in> section is used to load a class external to Struts. Its main attribute is classname, which specifies the name of the class to be instantiated. The instantiated object may need to be initialized. This is done using the set-property tag, which has two attributes:

  • property: the name of the property to initialize
  • value: the value of the property

Here, the DynaValidatorForm class needs to know two pieces of information:

  • the XML file that defines the standard integrity constraints that the class can validate.
  • the XML file defining the integrity constraints for the application’s various dynamic forms

This information is provided here by the pathnames property. The value of this property is a list of XML files that the validator will load:

  • validator-rules.xml is the file defining the standard integrity constraints. It is included with Struts. It can be found in <struts>\lib along with its DTD definition file:

Image

  • validation.xml defines the integrity constraints for the application’s various dynamic forms. It is created by the developer. Its name is arbitrary.

These two files can be placed anywhere under WEB-INF. In our example, they will be placed directly under WEB-INF:

Image

5.2. Writing integrity constraints for dynamic forms

The validation.xml file will contain our integrity constraints for the name and age fields of the frmPersonne form of type org.apache.struts.validator.DynaValidatorForm. Its content is as follows:

<form-validation>
    <global>
      <constant>
        <constant-name>positiveInteger</constant-name>
        <constant-value>^\s*\d+\s*$</constant-value>
    </constant>
  </global>      
    <formset>
        <form name="frmPerson">
          <field property="name" depends="required">
             <arg0 key="person.name"/>      
          </field>
          <field property="age" depends="required,mask">            
             <arg0 key="person.age"/>      
              <var>
                  <var-name>mask</var-name>
                  <var-value>${positiveInteger}</var-value>
              </var>
          </field>
    </form>
    </formset>
</form-validation>    

The following writing rules must be followed:

  • All rules are contained within a <form-validation> tag
  • The <global> tag is used to define information with global scope, i.e., valid for all forms if there are multiple forms. Here, we have placed constants within <global>. A constant is defined by its name (the <constant-name> tag) and its value (the <constant-value> tag). We define the constant `entierpositif` with the regular expression that must be verified for a positive integer: `_^\s*\d+\s*$` (a sequence of digits possibly preceded and/or followed by spaces).
  • The <formset> tag defines the set of forms for which there are integrity constraints to be checked
  • The <form name="unFormulaire"> tag is used to define the integrity constraints for a specific form, the one whose name is specified by the name attribute. This name must exist in the list of forms defined in struts-config.xml. Here, the frmPersonne form used is defined in struts-config.xml by the following section:
    <form-beans>
        <form-bean name="frmPersonne" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="name" type="java.lang.String" initial=""/>
            <form-property name="age" type="java.lang.String" initial=""/>
        </form-bean>            
  • A form tag contains as many <field> tags as there are integrity constraints to be checked for the form. A <field> tag has the following attributes:
    • property: name of the form field for which integrity constraints are defined
    • depends: list of integrity constraints to be checked.
      • The possible constraints are as follows: required (the field must not be empty), mask (the field value must match a regular expression defined by the mask variable), integer: the field value must be an integer, byte (byte), long (long integer), float (single-precision floating-point), double (double-precision floating-point), short (short integer), date (the field value must be a valid date), range (the field value must be within a given range), email: (the field value must be a valid email address), ...
      • Integrity constraints are checked in the order specified by the depends attribute. If a constraint fails, the subsequent ones are not tested.
      • Each constraint is linked to an error message defined by a key. Here are a few examples in the format constraint (key): required (errors.required), mask (errors.invalid), integer (errors.integer), byte (errors.byte), long (errors.long), ...
      • The error messages associated with the preceding keys are defined in the validator-rules.xml file:
   # Struts Validator Error Messages
   errors.required={0} is required.
   errors.minlength={0} cannot be less than {1} characters.
   errors.maxlength={0} cannot be longer than {1} characters.
   errors.invalid={0} is invalid.

   errors.byte={0} must be a byte.
   errors.short={0} must be a short.
   errors.integer={0} must be an integer.
   errors.long={0} must be a long.
   errors.float={0} must be a float.
   errors.double={0} must be a double.

   errors.date={0} is not a date.
   errors.range={0} is not in the range {1} through {2}.
   errors.creditcard={0} is an invalid credit card number.
   errors.email={0} is an invalid email address.
  • As we can see above, the messages are in English. Furthermore, they are included as comments in the file, which specifies that they should be placed in the application’s messages file. Recall that this is defined in a section of the struts-config.xml file:
    <message-resources 
      parameter="ressources.personneressources"
    null="false"
  />    

The parameter attribute indicates that the application's messages are in the file

WEB-INF/classes/resources/personneressources.properties.

Error messages should therefore be placed in this file. They are added to the existing ones:

person.form.name.empty=<li>You must enter a name</li>
person.form.age.empty=<li>You must enter an age</li>
person.form.age.incorrect=<li>The age is incorrect</li>
errors.header=<ul>
errors.footer=</ul>

   # Struts Validator error messages
   # The key is predefined and must not be changed
   # the associated error message is free-form
   # The message can have up to 4 parameters {0} to {3}

   errors.required=<li>The [{0}] field must be filled in.</li>
   errors.minlength=<li>The [{0}] field must contain at least {1} character.</li>
   errors.maxlength=<li>The [{0}] field cannot have more than {1} characters.</li>
   errors.invalid=<li>The [{0}] field is incorrect.</li>

   errors.byte=<li>{0} must be a byte.</li>
   errors.short=<li>{0} must be a short integer.</li>
   errors.integer=<li>{0} must be an integer.</li>
   errors.long=<li>{0} must be a long integer.</li>
   errors.float=<li>{0} must be a single-precision float.</li>
   errors.double=<li>{0} must be a double.</li>

   errors.date=<li>{0} is not a valid date.</li>
   errors.range=<li>{0} must be in the range {1} to {2}.</li>
   errors.creditcard=<li>{0} is not a valid card number.</li>
   errors.email=<li>{0} is not a valid email address.</li>

   person.name=name
     person.age=age

Let's go through the integrity constraints in the validation.xml file one by one to explain them:

    <formset>
        <form name="frmPersonne">
          <field property="name" depends="required">
             <arg0 key="person.name"/>      
          </field>
...
    </form>
    </formset>

Note that these integrity constraints apply to the name and age fields of a dynamic form defined in struts-config.xml:

        <form-bean name="frmPersonne" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="name" type="java.lang.String" initial=""/>
            <form-property name="age" type="java.lang.String" initial=""/>
        </form-bean>            

It is important that the names of the form and its fields are identical in both files. The integrity constraint on the name field (property="name") specifies that the field must not be empty (depends="required"). If this is not the case, an ActionError object with the key errors.required will be generated. The message associated with this key is found in the person.resources.properties file:

   errors.required=<li>The [{0}] field must be filled in.</li>

We can see that this message uses a {0} parameter. Its value is set by the <arg0> tag in the integrity constraint:

             <arg0 key="person.name"/>      

Here again, arg0 is designated by a key that is also found in the messages file:

   person.name=name

Putting it all together, the error message generated if the name field is not filled in is:

<li>The [name] field must be filled in.</li>

Let’s now analyze the second constraint, the one on the age field:

    <global>
      <constant>
        <constant-name>positiveInteger</constant-name>
        <constant-value>^\s*\d+\s*$</constant-value>
    </constant>
  </global>      
    <formset>
        <form name="frmPersonne">
...
          <field property="age" depends="required,mask">            
             <arg0 key="person.age"/>      
              <var>
                  <var-name>mask</var-name>
                  <var-value>${positiveInteger}</var-value>
              </var>
          </field>
    </form>
    </formset>

There are two constraints for the age field: required and mask. We can repeat the previous explanation for the required constraint. This means that the error message associated with this constraint will be:

<li>The [age] field must be filled in.</li>

The second constraint is mask. This means that the field’s content must match a pattern expressed by a regular expression. The value of this constraint is defined in a <var> tag that defines a variable named mask (<var-name>) with the value ${positiveInteger} (<var-value>). positiveInteger is a constant defined in the <global> section of the file with the value of the regular expression ^\s*\d+\s*$. The integrity constraint is therefore that the age must be a sequence of one or more digits, optionally preceded or followed by spaces. If this constraint is not met, an ActionError object with the key errors.invalid will be generated. In the messages file, this key is associated with the following message:

   errors.invalid=<li>The [{0}] field is incorrect.</li>

The constraint must define a value for the {0} parameter. It does so using the <arg0> tag:

             <arg0 key="person.age"/>      

In the message file, the key person.age is associated with the following message:

     person.age=age

The error message that will be generated if the mask constraint is not verified is therefore:

<li>The [age] field is incorrect.</li>

5.3. The application classes

In a Struts application, the classes to be written are those for forms (ActionForm or derived) and those for actions (Action or derived). In the new strutspersonne2 application, there is no longer a class for the form. The form content is defined in struts-config.xml and the associated integrity constraints in WEB-INF/validation.xml. In the strutspersonne1 application, the FormulaireAction class for processing the form was as follows:

package istia.st.struts.personne;

....

public class FormAction
  extends Action {
  public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response) throws IOException,
    ServletException {
    // we have a valid form; otherwise, we wouldn't have gotten this far
    DynaActionForm form = (DynaActionForm) form;
    request.setAttribute("name", form.get("name"));
    request.setAttribute("age", form.get("age"));
    return mapping.findForward("response");
  }//execute
}

The FormAction class expected to receive a form in the form of a DynaActionForm object (boxed code). However, in the struts-config.xml file, the form class is defined as follows:

        <form-bean name="frmPersonne" type="org.apache.struts.validator.DynaValidatorForm">
...
        </form-bean>            

The form will therefore be placed in a DynaValidatorForm object. It turns out that this class is derived from the DynaActionForm class. The execute method of our FormulaireAction class therefore remains valid. It does not need to be rewritten.

5.4. Deploying and testing the strutspersonne2 application

5.4.1. Creating the context

We have named this new application strutspersonne2. We create a new definition in the <tomcat>\conf\serveur.xml file of Tomcat 4.x:

                <Context path="/strutspersonne2" docBase="e:/data/serge/web/struts/personne2" />

Once this is done, Tomcat must be restarted. You can verify the validity of the context by requesting the URL:

http://localhost:8080/strutspersonne2/

Image

5.4.2. The Views

Copy the views folder from the strutspersonne1 application to the strutspersonne2 application folder. The views have not changed.

Image

5.4.3. The WEB-INF folder

Copy the WEB-INF folder from the strutspersonne1 application into the strutspersonne2 application folder. A few files have changed:

Image

The struts-config.xml configuration file becomes the following:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <form-beans>
        <form-bean name="frmPersonne" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="name" type="java.lang.String" initial=""/>
            <form-property name="age" type="java.lang.String" initial=""/>
        </form-bean>            
    </form-beans>

    <action-mappings>

      <action
          path="/main"
          name="frmPersonne"
            validate="true"
            input="/errors.do"
            scope="session"
          type="istia.st.struts.person.FormAction"
      >
            <forward name="response" path="/response.do"/>
        </action>

      <action
          path="/errors"
          parameter="/views/errors.person.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

      <action
          path="/response"
          parameter="/views/response.person.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

      <action
          path="/form"
          parameter="/views/person-form.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
    </action-mappings>

    <message-resources 
      parameter="person-resources"
    null="false"
  />    

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property 
        property="pathnames" 
      value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"
    />
    </plug-in>

</struts-config>

This file is identical to the one in the strutspersonne1 application, except for the dynamic definition of the form and the inclusion of the validation plugin (boxed sections).

Add the following validation.xml file to WEB-INF:

<form-validation>
    <global>
      <constant>
        <constant-name>positiveInteger</constant-name>
        <constant-value>^\s*\d+\s*$</constant-value>
    </constant>
  </global>      
    <formset>
        <form name="frmPerson">
          <field property="name" depends="required">
             <arg0 key="person.name"/>      
          </field>
          <field property="age" depends="required,mask">            
             <arg0 key="person.age"/>      
              <var>
                  <var-name>mask</var-name>
                  <var-value>${positiveInteger}</var-value>
              </var>
          </field>
    </form>
    </formset>
</form-validation>

Add the files validator-rules.xml and validator-rules_1_1.dtd, located in <struts>\lib, to WEB-INF:

Image

In the WEB-INF/classes folder, there is now only one class:

Image

In the WEB-INF\classes\resources folder, you will find the following message file, personneressources.properties:

person.form.name.empty=<li>You must enter a name</li>
person.form.age.empty=<li>You must enter an age</li>
person.form.age.incorrect=<li>The age is incorrect</li>
errors.header=<ul>
errors.footer=</ul>

   # Struts Validator error messages
   # The key is predefined and must not be changed
   # the associated error message is free-form
   # The message can have up to 4 parameters {0} to {3}

   errors.required=<li>The [{0}] field must be filled in.</li>
   errors.minlength=<li>The [{0}] field must contain at least {1} character.</li>
   errors.maxlength=<li>The [{0}] field cannot have more than {1} characters.</li>
   errors.invalid=<li>The [{0}] field is incorrect.</li>

   errors.byte=<li>{0} must be a byte.</li>
   errors.short=<li>{0} must be a short integer.</li>
   errors.integer=<li>{0} must be an integer.</li>
   errors.long=<li>{0} must be a long integer.</li>
   errors.float=<li>{0} must be a single-precision floating-point number.</li>
   errors.double=<li>{0} must be a double.</li>

   errors.date=<li>{0} is not a valid date.</li>
   errors.range=<li>{0} must be in the range {1} to {2}.</li>
   errors.creditcard=<li>{0} is not a valid card number.</li>
   errors.email=<li>{0} is not a valid email address.</li>

   person.name=name
  person.age=age

Image

5.5. Tests

We are ready for testing. Below are some screenshots that the reader is invited to reproduce.

We request the URL http://localhost:8080/strutspersonne2/formulaire.do:

Image

We click the [Submit] button without filling in the fields:

Image

We try again with an error in the age field:

Image

We get the following response:

Image

Try again, this time entering the correct values:

Image

We get the following response:

Image

5.6. Conclusion

We have shown that using dynamic forms with "standard" integrity constraints avoids the need to create classes to represent them. If the integrity constraints deviate from the standard, then we are once again required to create classes to verify these new constraints.