Can you change an annotation message at run time?

coder picture coder · May 18, 2011 · Viewed 16.3k times · Source

I'm trying to include a dynamic message in my annotation that changes the main body of the text based on the values that are found in the other variables that are passed to it. I set a default message, but when a certain indicator is set, I want to display a different message. Is this possible?

Here's my annotation -

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

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

Here's the validator class used by the annotation -

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

    @Override
    public void initialize(FieldMatch constraintAnnotation)
    {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
        thirdFieldName = constraintAnnotation.third();
        match = constraintAnnotation.match();
        if(match != null && !Boolean.getBoolean(match)){
            message = "error.theseValuesMustNotMatch";
        }
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context)
    {
        try
        {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
            final Object thirdObj = BeanUtils.getProperty(value, thirdFieldName);
            final String same = BeanUtils.getProperty(value, match);

            boolean valid = false;
            if(same != null && Boolean.getBoolean(same)){
                if("".equals(thirdObj)){
                    valid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj) ;
                }
                else{
                    valid = firstObj != null && firstObj.equals(secondObj) && firstObj.equals(thirdObj) ;   
                }
            }
            else{
                if("".equals(thirdObj)){
                    valid = firstObj == null && secondObj == null || firstObj != null && !firstObj.equals(secondObj) ;
                }
                else{
                    valid = firstObj != null && !(firstObj.equals(secondObj) && firstObj.equals(thirdObj)) ;   
                }
            }
            return valid ;
        }
        catch (final Exception ignore)
        {
            // ignore
        }
        return true;
    }
}

The piece I'm most interested in is the code that reads -

    if(match != null && !Boolean.getBoolean(match)){
        message = "password.error.theseValuesMustNotMatch";
    }

Answer

coder picture coder · May 19, 2011

Here's how I was able to do this -

@Override
public void initialize(FieldMatch constraintAnnotation)
{
    firstFieldName = constraintAnnotation.first();
    secondFieldName = constraintAnnotation.second();
    thirdFieldName = constraintAnnotation.third();
    match = constraintAnnotation.match();

    //set a message variable on initialization    
    if("true".equals(match)){
        message = constraintAnnotation.message();
    }
    else{
        message = "{password.error.threeQuestionsSameAnswer}";}
}

@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
    Object firstObj = null;
    Object secondObj = null;
    Object thirdObj = null;

    //disable existing violation message
    context.disableDefaultConstraintViolation();
    //build new violation message and add it
    context.buildConstraintViolationWithTemplate(message).addConstraintViolation();

etc.........
}