Building Dynamic ConstraintViolation Error Messages
Asked Answered
C

2

13

I've written a validation annotation implemented by a custom ConstraintValidator. I also want to generate very specific ConstraintViolation objects that use values computed during the validation process during message interpolation.

public class CustomValidator 
  implements ConstraintValidator<CustomAnnotation, ValidatedType> {

...
  @Override
  public boolean isValid(ValidatedType value, ConstraintValidatorContext context) {
    // Figure out that the value is not valid.
    // Now, I want to add a violation whose error message requires arguments.
  }
}

A hypothetical error message in my message source:

CustomAnnotation.notValid = The supplied value {value} was not valid because {reason}.

The context passed into the isValid method provides an interface for building up a constraint violation, and finally adding it to the context. However, I can't seem to figure out how to use it. According to this documention for the version I'm using, I can add bean and property nodes to the violation. These are the only additional details I can specify to the violation definition, but I don't understand how they might map to parameters in the error message.

Million dollar question: how can I pass dynamic parameters to my validation error messages using a custom validator? I would like to fill in those {value} and {reason} fields using the ConstraintValidatorContext's interface for building violations.

Obtaining an instance of the message source and interpolating the message within the custom validator is not an option - the messages coming out of validation get interpolated no matter what, and interpolating internally will result in some messages being interpolated twice, potentially annihilating escaped single quotes or other characters with special meanings in my message definitions file.

Conviction answered 16/5, 2014 at 19:13 Comment(0)
C
25

That's not possible with the standardized Bean Valiation API, but there is a way in Hibernate Validator, the BV reference implementation.

You need to unwrap the ConstraintValidatorContext into a HibernateConstraintValidatorContext which gives you access to the addExpressionVariable() method:

public class MyFutureValidator implements ConstraintValidator<Future, Date> {

    public void initialize(Future constraintAnnotation) {}

    public boolean isValid(Date value, ConstraintValidatorContext context) {
        Date now = GregorianCalendar.getInstance().getTime();

        if ( value.before( now ) ) {
            HibernateConstraintValidatorContext hibernateContext =
                context.unwrap( HibernateConstraintValidatorContext.class );

            hibernateContext.disableDefaultConstraintViolation();
            hibernateContext.addExpressionVariable( "now", now )
                .buildConstraintViolationWithTemplate( "Must be after ${now}" )
                .addConstraintViolation();

            return false;
        }

        return true;
    }
}

The reference guide has some more details.

Coulee answered 19/5, 2014 at 6:38 Comment(1)
For the record, I accepted this answer because you stated that this isn't possible. The example from the documentation actually didn't work out for me because my constraint violation templates themselves are message codes, and I was unable to access the custom "expression variables" in the final message. (The error message was always that the code could not be resolved.)Conviction
H
3

If you use message codes you can simply add something like {0} in them.

For example: "The field {0} must not be empty."

And then use hibernateContext.addMessageParameter("0", fieldName); instead of addExpressionVariable(...).

That worked for me.

Hooligan answered 22/3, 2018 at 10:47 Comment(1)
Additionally, if you use message codes and want to pass the value of the field being validated - you can access it via ${validatedValue} - e.g. hibernateContext. addExpressionVariable("0", fieldName) "The field {0} must not be empty. Its value is ${validatedValue}"Supposal

© 2022 - 2024 — McMap. All rights reserved.