Monday, June 4, 2012

Struts2 XML VALIDATION

Struts 2 framework allows powerful and easy validation through Java, XML or Annotations.
The first way (override of Validate() method) should be avoided.
Both XML and Annotations are good ways to go, but as usual they're poorly documented / exampled.
Let's take a look at XML Validation:


STANDARD XML VALIDATION

Scenario:
You have a JSP that posts a form with new data (let's say one record about a person) to be inserted, to an Action named InsertOneRowAction.java.
Data posted are: Name and Birthday (KISS paradigm ftw...).
Fields in JSP page are named person.name and person.birthday (getters of Person.java value object).
Both fields are mandatory, and Birthday must be a valid date between 1990 and 2000.

Solution:
Create a file with the name of the action and append "-Validation.xml" at the end, in the same package of the action.
We will see then:
com.andrealigios.struts2validation.presentation.action.InsertOneRowAction.java
com.andrealigios.struts2validation.presentation.action.InsertOneRowAction-Validation.xml


Open XML validation file and write the desired rules in there, in this case:

  <field name="person.name">
      <field-validator type="requiredstring">
          <message><![CDATA[ Name is mandatory ]]></message>
      </field-validator>
  </field>
  <field name="person.birthday">
        <field-validator type="required">
            <message><![CDATA[ Birthday is mandatory ]]></message>
        </field-validator>
        <field-validator type="date">
            <param name="min">01/01/1990</param>
            <param name="max">01/01/2000</param>
            <message><![CDATA[ Birthday must be a valid date between ${min} and ${max} ]]></message>
        </field-validator>
    </field>



Now put the tag <fielderrors/> into your JSP.
Done!

Every time your data posted won't match the validation criteria, the Validation Interceptor will redirect your post to the source JSP page, showing the error messages and preventing wrong / incomplete data to reach the action's setters.


DYNAMIC XML VALIDATION - FIELDEXPRESSION VALIDATOR

Let's dig a bit deeper: we need min and max Date parameters to be dynamic, for example read from a database or a configuration file.
We need to create a getter for each parameter, that does its business inside, and returns the desired java.util.Date.
We can put the getter into the Action, or into Actions that are extended by ours (let's say BaseAction). Remind to preserve getters's visibility ;)

 
public Date getMinDateDynamicallyRead(){
 // Do business
 return minDate; 
}

public Date getMaxDateDynamicallyRead(){
 // Do business
 return maxDate; 
}


Now, let's modify XML Validation file as follows:

  <field name="person.birthday">
        <field-validator type="required">
            <message><![CDATA[ Birthday is mandatory ]]></message>
        </field-validator>
        <field-validator type="date">
            <message><![CDATA[ Birthday must be a valid date ]]></message>
        </field-validator>
      <field-validator type="fieldexpression">
        <param name="expression"> 
          <![CDATA[ person.birthday==null || (person.birthday >= minDateDynamicallyRead && person.birthday <=  maxDateDynamicallyRead) ]]>
        </param> 
        <message><![CDATA[ Birthday must be between ${minDateDynamicallyRead} and ${maxDateDynamicallyRead} ]]></message>
         </field-validator>
    </field>



The person.birthday == null part is necessary to avoid redundancy of messages. If date is not provided, then the "required" validator must raise the error message, but there's no need to show that date is not inside our desired interval too...

Note that you can chain multiple "fieldexpression" validators for a single field, performing different controls and showing different messages.


MULTIPLE ROWS XML VALIDATION - VISITOR VALIDATOR

Now let's understand the most powerful, less documented feature of Struts 2 Validation : VISITOR validation.

Scenario:
We are on a different JSP page, that shows us a list of persons, and allows us to perform multiple changes to their Names and Birthdays, and to save 'em all with one click on the UPDATE button.
The main object into the JSP is PersonsTable.java (exposed through a getter from the Action), that exposes a list of Person.java objects, cycled within an iterator in the JSP:


public List<Person> getPersonsFromDatabase(){ 
    return savedPersons; 
}

The JSP UPDATE button posts to an Action named UpdateMultipleRowsAction.java
(We obviously can't validate the persons in the previous way, because we can't (and definitely don't want to) write a huge, static XML file specifying row indexes ;)

Struts2 allows us to resolve the problem like follows:

Create the usual XML Validation file in action's package, beside the action:
com.andrealigios.struts2validation.presentation.action.UpdateMultipleRowsAction.java
com.andrealigios.struts2validation.presentation.action.UpdateMultipleRowsAction-Validation.xml

inside the xml file, let's write:

<validators>
  <field name="personsTable.personsFromDatabase">
      <field-validator type="visitor">
          <message></message>
      </field-validator>
  </field>
</validators>


This command will tell Struts to perform, for EVERY single row, the validation using the validation rules of the object listed (in this case, Person.java).
The real validation must be written into an xml file UNDER THE OBJECT PACKAGE (NOT the Actions package), with a name composed by the Object class concatenated to "-Validation.xml".

For example:
com.andrealigios.struts2validation.valueobject.Person.java
com.andrealigios.struts2validation.valueobject.Person-Validation.xml

Inside Person-Validation.xml, we can put the same rules used in previous examples, pointing to the name of the field (name of the getters of Person.java, in this case "name" or "birthday", without "person" or other stuff before), as follows:


  <field name="name">
      <field-validator type="requiredstring">
          <message><![CDATA[ Name is mandatory ]]></message>
      </field-validator>
  </field>
  <field name="birthday">
        <field-validator type="required">
            <message><![CDATA[ Birthday is mandatory ]]></message>
        </field-validator>
        <field-validator type="date">
            <param name="min">01/01/1990</param>
            <param name="max">01/01/2000</param>
            <message><![CDATA[ Birthday must be a valid date between ${min} and ${max} ]]></message>
        </field-validator>
    </field>


Enjoy, forgive my Engrish and... comment!! ;)



2 comments:

  1. Great explanation...i read some of your answers on stackoverflow and then followed from there to your blog. Liked this part on validation very much as till now i had this fear of xml validation bcoz of which i am using validate() till now. Now with your examples i will try to switch over.
    Thanks again

    ReplyDelete
  2. Hi, Great article.
    Can you please provide a list of possible field-validator types ?
    I can't find them anywhere.
    Thanks

    ReplyDelete