Monday, July 02, 2007

Struts: Creating Custom Validator

Creating Custom Validator

Idea was to create custom password validator which will not allow certain words to be password. This included common words like password, companyname, abc, 123 etc. Also this requirement will be extended in future to disallow password being same as first name, last name, phone numbers, email address etc.

As direct implementation one can always override validate method in the Action Form class for the form. But since password field existed in multiple forms it made more sense to make generic validator which can be added to validation-rules.xml file for the application.

Add following section in validation-rules.xml file. This describes validation rule.
<validator name="custompassword" classname="com.web.helper.CustomValidationUtility" method="validatePassword" methodparams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionMessages, org.apache.commons.validator.Validator, javax.servlet.http.HttpServletRequest" depends="" msg="errors.password"></validator>


Add the rule for the form that we need to validate.

<!-- change password -->
<form name="/change-password">
...
<field property="newPassword" depends="custompassword">
...
</field>
</form>




Create custom validator classes as

package com.web.helper;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.validator.Resources;

public class CustomValidationUtility implements Serializable
{

public static boolean validatePassword(
Object bean,
ValidatorAction va,
Field field,
ActionMessages messages,
Validator validator,
HttpServletRequest request) {

System.out.println("Here now");
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
List invalidPasswordList = new ArrayList();
invalidPasswordList.add("password");
invalidPasswordList.add("companyname");
invalidPasswordList.add("abc");
invalidPasswordList.add("123");

if (!GenericValidator.isBlankOrNull(value)) {
try {
if (invalidPasswordList.contains(value)) {
messages.add(field.getKey(),
Resources.getActionError(
request,
va,
field));

return false;
}
} catch (Exception e) {
messages.add(field.getKey(),
Resources.getActionError(
request,
va,
field));
return false;
}
}

return true;
}
}


That's it.
Roadblocks:

  • One of the problem I faced while working on this in eclipse + wtp that the class was not included in class path automatically. I have to stop server + Compile Clean + Publish + Restart the server for each change I made. I struggled with following exceptions. First was loadValidationActionClass method threw ValidatorException. This is because the my ValidationAction class i.e. CustomValidationUtility was not in classpath. I hoped that WTP will take care of this dynamically, but for some reason it didn't

  • Second exception was regarding the parameter of my validation method, they have to exactly match as given in validation-rules.xml.

  • Third problem faced was whether to use ActionErrors or ActionMessages as parameter. There are numerous example out on the web for custom validator to use ActionErrors. It gave me NullPointerException. On debugging I found out that errors parameter in the method was null. I switched it to ActionMessages (which is parent class of ActionError) and it worked. (Of course, WTP made me do entire trip of stopping, cleaning, compiling, publishing, and starting)

  • Some other problems other people faced working on this (which I found out while google-ing): You have to be absolutely sure which version of Struts and commons validator you are using. Struts 1.1 goes with commons-validator 1.1 and Struts 1.2 goes with Commons-validator 1.3. People spent frustrating amount of effort to resolve error only to find out they have mismatched library.


Usage and/or Extension of above idea
If developing commons validator based validation mechanism, above methods can be used to come up with specific validation for those fields which are going to used at multiple places. Advantages of such mechanism is it will save you from writing and debugging java script, can come up with architecture where you can validate it with databases - though approach needs to be well thought out and used sparingly. (e.g. list of password could have been stored in database tables), comparison with other fields on the page. In such case, I believe that validate method in form should be put to use rather than generic validations. Also above mechanism for validation can be used when your validation is interdependent on multiple fields on the same form.

No comments: