Loc Nguyen
Loc Nguyen

Reputation: 9562

How do I dynamically resolve message parameters with Hibernate Validator?

I'm using Hibernate Validator and would like to resolve the category's name in an error message. Consider this simple scenario:

public class Category {
    private String name;
}

public class Product {
    @HazardousCategoryConstraint(message = "{haz.cat.error}")
    private Category category;
    private String name;
}

public class InventoryReport {
    @Valid
    private List<Product> products;
}


ValidationMessages.properties
haz.cat.error={name} is a product in the hazardous category list.

Assume that I have a working implementation of HazardousCategoryConstraint. The validator checks each Category's name against a list of restricted names. When I call validate(InventoryReport) I get the number of errors I expect except they are the same string. I'd like to see the Category's name resolved into each message. Can someone point me to an example of how to resolve parameters dynamically, or show me how to?

Upvotes: 20

Views: 14540

Answers (2)

uniberg
uniberg

Reputation: 21

public boolean isValid(FooEntity fooEntity, ConstraintValidatorContext context) {


  //do some validation
  boolean result = ...;

  if (!result) {
    HibernateConstraintValidatorContext hibernateContext =
        context.unwrap(HibernateConstraintValidatorContext.class);
    hibernateContext.disableDefaultConstraintViolation();
    hibernateContext
        .addMessageParameter("answer", "Like This!")
        .addExpressionVariable("answer", "Like This!")
        .buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
        .addConstraintViolation();
result = false;
  }
  return result;
}

in the file:

.../resources/ValidationMessages.properties

com.example.validation.DaysLater.message = How do I dynamically resolve message parameters with Hibernate Validator? {answer}

Upvotes: 2

dira
dira

Reputation: 30594

IMO, the simple solution is to create custom implementation of javax.validation.MessageInterpolator. Delegate the main work to Hibernate Validator's ResourceBundleMessageInterpolator and do the required replacement work in CustomMessageInterpolator.

public class CustomMessageInterpolator extends org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator {

    private static final Pattern MESSAGE_PARAMETER_PATTERN = Pattern.compile( "(\\{[^\\}]+?\\})" );

    @Override
    public String interpolate(String message, Context context) {
        String resolvedMessage = super.interpolate(message, context);
        resolvedMessage = replacePropertyNameWithPropertyValues(resolvedMessage, context.getValidatedValue());
        return resolvedMessage;
    }

    private String replacePropertyNameWithPropertyValues(String resolvedMessage, Object validatedValue) {
        Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher( resolvedMessage );
        StringBuffer sb = new StringBuffer();

        while ( matcher.find() ) {
            String parameter = matcher.group( 1 );

            String propertyName = parameter.replace("{", "");
            propertyName = propertyName.replace("}", "");

            PropertyDescriptor desc = null;
            try {
                desc = new PropertyDescriptor(propertyName, validatedValue.getClass());
            } catch (IntrospectionException ignore) {
                matcher.appendReplacement( sb, parameter );
                continue;
            }

            try {
                Object propertyValue = desc.getReadMethod().invoke(validatedValue);
                matcher.appendReplacement( sb, propertyValue.toString() );
            } catch (Exception ignore) {
                matcher.appendReplacement( sb, parameter );
            }
        }
        matcher.appendTail( sb );
        return sb.toString();
    }

}

@Test

public void validate() {
        Configuration<?> configuration = Validation.byDefaultProvider().configure();
        ValidatorFactory validatorFactory = configuration.messageInterpolator(new CustomMessageInterpolator()).buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        Product p = new Product();
        Category cat = new Category();
        cat.setName("s"); //assume specified name is invalid
        p.setCategory(cat);

        Set<ConstraintViolation<Product>> violations = validator.validate(p);
        for(ConstraintViolation<Product> violation : violations) {
            System.out.println(violation.getMessage());
        }
    }

Output

s is a product in the hazardous category list.

Upvotes: 11

Related Questions