0

I have a constraint on which I want to use EL to tune the message to the circumstances. Dependent on the length of an array, I want to show a different message. However, I'm having trouble getting the length of that array.

What am I doing wrong?

import org.junit.jupiter.api.Test;
import javax.validation.*;
import java.lang.annotation.*;
import static org.assertj.core.api.Assertions.assertThat;

public class FooTest {
    private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    @Test
    public void foo() {
        var violations = validator.validate(new ObjectWithFoo());
        assertThat(violations).extracting("message")
            .containsExactly("Field value should be foo");
    }

    @Test
    public void foos() {
        var violations = validator.validate(new ObjectWithFoos());
        assertThat(violations).extracting("message")
            .containsExactly("Field value should be one of [foo, bar, baz]");
    }

    @Foo(foos = {"foo"})
    private static class ObjectWithFoo{}

    @Foo(foos = {"foo", "bar", "baz"})
    private static class ObjectWithFoos{}

    @Constraint(validatedBy = FooValidator.class)
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Foo{
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};

        String[] foos();

        // This is the message I want to tune to the length of the array.
        // If the array contains just one element, I want to show a different message.
        // Note that 'one of foos' is a placeholder; I still need to figure out 
        // how to display the array in that case.
        String message() default "Field value should be ${foos.length == 1 ? foos[0] : 'one of foos'}";
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @interface List {
            Foo[] value();
        }
    }

    public static class FooValidator implements ConstraintValidator<Foo, Object> {
        @Override
        public void initialize(Foo constraintAnnotation) {
        }

        @Override
        public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
            return false; // for this test, we want the validation to fail
        }
    }
}

Unfortunately, this throws an exception:

20:03:52.810 [main] WARN org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver - 
HV000148: An exception occurred during evaluation of EL expression '${foos.length == 1 ? foos[0] : 'one of $foos'}'
java.lang.NumberFormatException: For input string: "length"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.base/java.lang.Integer.parseInt(Integer.java:652)
SQB
  • 3,926
  • 2
  • 28
  • 49
  • 1
    A not very good solution would be something like: `foo.stream().count() == 1`. I've opened an issue for that: https://github.com/eclipse-ee4j/el-ri/issues/175 – Thiago Henrique Hupner Nov 30 '21 at 20:37
  • Just to explain a little more, the specification of ArrayELResolver::getValue states that `It accepts any object as a property and coerces that object into an integer index into the array`. You can see that in the exception, it tried to coerce the length to int. – Thiago Henrique Hupner Nov 30 '21 at 20:46
  • https://javadoc.io/doc/jakarta.el/jakarta.el-api/latest/index.html – Thiago Henrique Hupner Nov 30 '21 at 20:46
  • @ThiagoHenriqueHupner _that's_ why it did that! I thought it was because I was comparing it with a number. This is more than a comment, this is an answer. – SQB Dec 01 '21 at 22:12
  • @ThiagoHenriqueHupner Unfortunately, your "not very good solution" as you call it, leads to a DisabledFeatureELException telling me that "Method execution is not supported when only enabling Expression Language bean property resolution", a message so rare that it yields just one single result on Google. I understand what it means, but have no idea how to proceed. – SQB Dec 02 '21 at 11:07
  • Oh, that's unfortunate. I don't have a lot of experience with Bean Validation, so it seems that it doesn't like method calls in their expressions. – Thiago Henrique Hupner Dec 02 '21 at 13:04
  • 1
    Maybe this can help: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#section-interpolation-with-message-expressions – Thiago Henrique Hupner Dec 02 '21 at 13:04
  • @ThiagoHenriqueHupner since it is now fixed (thanks!), perhaps add an answer that states that? – SQB May 15 '23 at 13:02

0 Answers0