Cross field validation with HibernateValidator displays no error messages

Tiny picture Tiny · Aug 9, 2012 · Viewed 11.5k times · Source

I'm validating two fields, "password" and "confirmPassword" on the form for equality using HibernateValidator as specified in this answer. The following is the constraint descriptor (validator interface).

package constraintdescriptor;

import constraintvalidator.FieldMatchValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch
{
    String message() default "{constraintdescriptor.fieldmatch}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    /**
     * @return The first field
     */
    String first();

    /**
     * @return The second field
     */
    String second();

    /**
     * Defines several <code>@FieldMatch</code> annotations on the same element
     *
     * @see FieldMatch
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    public @interface List{
        FieldMatch[] value();
    }
}

The following is the constraint validator (the implementing class).


package constraintvalidator;

import constraintdescriptor.FieldMatch;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.BeanUtils;

public final class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
{
    private String firstFieldName;
    private String secondFieldName;

    public void initialize(final FieldMatch constraintAnnotation) {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
        //System.out.println("firstFieldName = "+firstFieldName+"   secondFieldName = "+secondFieldName);
    }

    public boolean isValid(final Object value, final ConstraintValidatorContext cvc) {
        try {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName );
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName );
            //System.out.println("firstObj = "+firstObj+"   secondObj = "+secondObj);
            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        }
        catch (final Exception e) {
            System.out.println(e.toString());
        }
        return true;
    }
}

The following is the validator bean which is mapped with the JSP page (as specified commandName="tempBean" with the <form:form></form:form> tag).

package validatorbeans;

import constraintdescriptor.FieldMatch;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;

@FieldMatch.List({
    @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match", groups={TempBean.ValidationGroup.class})
})

public final class TempBean
{        
    @NotEmpty(groups={ValidationGroup.class}, message="Might not be left blank.")
    private String password;
    @NotEmpty(groups={ValidationGroup.class}, message="Might not be left blank.")
    private String confirmPassword;

    public interface ValidationGroup {};

    //Getters and setters                
}

UPDATE

It's all working correctly and does the validation intended. Just one thing remains is to display the specified error message above the TempBean class within @FieldMatch is not being displayed i.e only one question : how to display error messages on the JSP page when validation violation occurs?

(the annotation @NotEmpty on both of the fields password and confirmPassword in the TempBean class works and displays the specified messages on violation, the thing is not happening with @FieldMatch).

I'm using validation group based on this question as specified in this blog and it works well causing no interruption in displaying error messages (as it might seem to be).


On the JSP page these two fields are specified as follows.

<form:form id="mainForm" name="mainForm" method="post" action="Temp.htm" commandName="tempBean">

    <form:password path="password"/>
    <font style="color: red"><form:errors path="password"/></font><br/>

    <form:password path="confirmPassword"/>
    <font style="color: red"><form:errors path="confirmPassword"/></font><br/>

</form:form>

Answer

Eugene picture Eugene · Aug 19, 2012

Could you try your isValid method to be like this? (this is certainly working for me in live project):

 public boolean isValid(final Object value, final ConstraintValidatorContext cvc){
    boolean toReturn = false;

    try{
        final Object firstObj = BeanUtils.getProperty(value, firstFieldName );
        final Object secondObj = BeanUtils.getProperty(value, secondFieldName );

        //System.out.println("firstObj = "+firstObj+"   secondObj = "+secondObj);

        toReturn = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
    }
    catch (final Exception e){
        System.out.println(e.toString());
    }
    //If the validation failed
    if(!toReturn) {
        cvc.disableDefaultConstraintViolation();
        //In the initialiaze method you get the errorMessage: constraintAnnotation.message();
        cvc.buildConstraintViolationWithTemplate(errorMessage).addNode(firstFieldName).addConstraintViolation();
    }
    return toReturn;
}

Also I see that you are implementing the ConstraintValidator interface with an Object, literally. It should be the backing object that you have from your form:

tempBean // the one that you specify in the commandName actually.

So you implementation should like this:

 implements ConstraintValidator<FieldMatch, TempBean>

This is probably not the issue here, but as a future reference, this is how it should be.

UPDATE

Inside your FieldMatch interface/annotation you have two methods : first and second, add one more called errorMessage for example:

  Class<? extends Payload>[] payload() default {};

/**
 * @return The first field
 */
String first();

/**
 * @return The second field
 */
String second();

/**
  @return the Error Message
 */
String errorMessage

Look inside you method from the Validation class - you are getting the first and second field names there., so just add the errorMessage, like this for example:

  private String firstFieldName;
  private String secondFieldName;
  //get the error message name
  private String errorMessagename; 
public void initialize(final FieldMatch constraintAnnotation)
{
    firstFieldName = constraintAnnotation.first();
    secondFieldName = constraintAnnotation.second();
    errorMessageNAme = constraintAnnotation.errorMessage(); 

    //System.out.println("firstFieldName = "+firstFieldName+"   secondFieldName = "+secondFieldName);
}

Inside isValida get it, the same way you do for first and second field name and use it.