Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSR303: Trying to customize a constraint violation to be associated with a sub-path in a class-level relationship constraint validator

I am using JSR303 and created a class-level constraint that compares the password and its confirmation in a form, which I shall name here the @SameAs constraint. Ideally, I would have liked to associate the constraint with the intended target (confirmPassword), but obviously the enclosing bean is not available for extracting the password prop. - hence the class-level constraint.

I have read with interest other posts demonstrating how to leverage class-level constraints for validating relationships, but can't find anything explaining how the constraint violation can be customized to be associated with a sub-path, in this case one of the two fields in the relationship.

My question is the following: how would I associate the message from the constraint violation with the 'confirmPassword' field instead of the top-level object? I have tried to use the context paramter of javax.Validator.validate( target, context), but adding a Node within the validator for @SameAs causes an exception for the next constraint in the cascade (trying to extract the confirmPassword -> orderNumber property instead of order -> orderNumber ) as a result.

For the moment I have resorted to an ugly kludge by creating an extra property storing the constraint message which is plucked out for use near the confirmPassword input field on the web layer.

Surely I am missing something here....please see example below

Thanks for any comments

EXAMPLE

@Constraint( validatedBy = { SamePwdAsValidator.class})
public interface SamePwdAs {//...
}

//Using passwords in an order doesn't make sense - only for demo purpose
@SamePwdAs( message = "Password and confirmation must match" ...)
public class Order {

    @NotNull
    @Size(....)
    String pwd;

    //where I would really like to use @SameAs, and associate a violation 
    String pwdConfirm;

    @NotNull (...)
    @Pattern (....)
    String orderNumber;

    //...getters/setters
}

public class SamePwdAsValidator implements javax.validation.Validator {
//...
  public boolean isValid( Object target, ValidationContext ctx) {
   String tgt = target.getPwd(), other = target.getPwdConfirm()
   boolean isValid = tgt.equals( other);

   if ( !isValid) {
      //try to configure the context subpath for pwdConfirm to associate this constraint violation with: I tried 
      //ctx.addNode( 'pwdConfirm').addConstraintViolation() which doesn't work, as the next validator will
      //bump into trying to extract Order.pwdConfirm.orderNumber and throw a NoPropertyFoundException or the like
   }

   return isValid;
}
like image 557
KingCode Avatar asked Feb 08 '11 12:02

KingCode


1 Answers

I'm going to give 2 answers.

Answer 1, which endeavours to answer your question:

I use a utility method like so:

public static final void recreateConstraintViolation(ConstraintValidatorContext constraintValidatorContext, String errorCode, String fieldName) {
        constraintValidatorContext.disableDefaultConstraintViolation();
        constraintValidatorContext.buildConstraintViolationWithTemplate(errorCode).
        addNode(fieldName).addConstraintViolation();

}

And then, in your validator where you compare the two passwords:

if ( !isValid) {
    for (String fieldName : fieldNames) {
        CustomConstraintUtils.recreateConstraintViolation(constraintValidatorContext, "password.password2.mismatch", fieldName);
    }
    return false;
}

Answer 2

I'd suggest you use a security framework like Spring Security for taking care of password matching, as I presume your use-case is dealing with a user logon.

like image 65
opyate Avatar answered Oct 18 '22 11:10

opyate