0
votes

I'm working on a project and I've been using Hibernate Validator for a while. Recently I tried to make use of Interpolation with message expressions (with EL features) in my validation messages, but I get inconsistent results (to my knowledge).

According to the documentation on message interpolation steps, message parameters (those enclosed in "{..}") are resolved prior to evaluation of message expressions (those enclosed in "${..}"). Hence, I conclude that it is allowed to write a message like this:

    ${validatedValue > someValue ? '{x.y.z.message}' : '{x.y.w.message}'}

and expect that a message literal whose key is either x.y.z.message or x.y.w.message be the result, where x.y.z.message and x.y.w.message are assumed to be valid and already defined message keys in a properly bootstrapped resource bundle.

To clarify things out, I've written a test case using TestNG and ran it against hibernate-validator-5.0.1.Final and hibernate-validator-5.1.0-Alpha. As is pointed out in test comments, the results obtained by these releases vary themselves on this specific issue which hints me towards an implementation bug. I first thought this might have to do with [HV-798] but it seems deeper.

Here is the test case:

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.constraints.Size;

import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

public class HibernateValidationTest
{
    public class Entity
    {
        public int  caseIndex;

        @Size(min = 4, max = 10, message = "${validatedValue.length() < min ? 'Error msg.#1' : 'Error msg.#2'}")
        public String getMessage1()
        {
            if (caseIndex == 0)
                return "abc";
            return "abcd";
        }

        @Size(min = 4, max = 10, message = "{javax.validation.constraints.Size.message}")
        public String getMessage2()
        {
            if (caseIndex == 1)
                return "abc";
            return "abcd";
        }

        @Size(min = 4, max = 10, message = "{javax.validation.constraints.Size.message} (${validatedValue.length() < min ? 'Error msg.#3' : '{javax.validation.constraints.Size.message}'})")
        public String getMessage3()
        {
            if (caseIndex == 2)
                return "abc";
            return "abcd";
        }
    }

    @Test
    public void testValidatorBug()
    {
        Validator validator = javax.validation.Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Entity>> violations;
        ConstraintViolation<Entity> violation;
        Entity entity = new Entity();

        /**
         * PASS
         */
        entity.caseIndex = 0;
        violations = validator.validate(entity);
        assertEquals(violations.size(), 1);
        violation = violations.iterator().next();
        assertEquals(violation.getMessage(), "Error msg.#1");

        /**
         * PASS
         */
        entity.caseIndex = 1;
        violations = validator.validate(entity);
        assertEquals(violations.size(), 1);
        violation = violations.iterator().next();
        assertEquals(violation.getMessage(), "size must be between 4 and 10");

        /**
         * FAIL
         * 
         * Violation message:
         * 
         * - on version hibernate-validator-5.0.1.Final: 
         *  size must be between 4 and 10 (${validatedValue.length() < min ? 'Error msg.#3' : '{javax.validation.constraints.Size.message}'})
         * 
         * - on hibernate-validator-5.1.0-20131114.015215-37: 
         *  {javax.validation.constraints.Size.message} (${validatedValue.length() < min ? 'Error msg.#3' : '{javax.validation.constraints.Size.message}'})
         */
        entity.caseIndex = 2;
        violations = validator.validate(entity);
        assertEquals(violations.size(), 1);
        violation = violations.iterator().next();
        assertEquals(violation.getMessage(), "size must be between 4 and 10 (Error msg.#3)");
    }
}

In this test Entity.getMessage3() demonstrates the issue which is caught by last part of the test. Based on my understanding, this message must be size must be between 4 and 10 (Error msg.#3), but it is size must be between 4 and 10 (${validatedValue.length() < min ? 'Error msg.#3' : '{javax.validation.constraints.Size.message}'}) on version 5.0.1 and {javax.validation.constraints.Size.message} (${validatedValue.length() < min ? 'Error msg.#3' : '{javax.validation.constraints.Size.message}'}) on 5.1.0.Alpha.

Can anyone kindly evaluate this and assert the issue or help me find my own mistake.

Thank you for your time.

2

2 Answers

0
votes

This seems to be related to the nesting of message parameters within EL expressions.

Reading the spec wording, I don't see a reason why this should not be allowed, but I'm not sure we intentionally meant to support this case (I think we'd have provided an example in this case). I have forwarded this question to see what others on the BV EG think.

Hibernate Validator currently detects such nesting and raises an exception in that case. That's why the interpolation message stays unchanged (this is mandated by the spec).

0
votes

After flipping around this I decided to follow @Gunnar recommendation and since we're using OSGi I managed to do a quick Bundle Class Path trick to have the modified ResourceBundleMessageInterpolator place before the original one which is automatically picked by HV.