cmaynard
cmaynard

Reputation: 2860

Using Spring @Value inside @Size

I am using Spring Boot and javax validation, particularly @Size. I am trying to grab the values for size constraints from the application.properties file:

@Size(min= @Value("${device.name.minsize}"), max=@Value("${device.name.maxsize}"))
private String name;

But I receive the following compile time error:

Error:(26, 16) java: annotation not valid for an element of type int

Trying to fix this issue I'm attempting the following:

@Size(min=Integer.parseInt( @Value("${device.name.minsize}") ), max=Integer.parseInt( @Value("${device.name.maxsize}") ) )

But this has multiple errors as well.

How can I convert the @Value annotations correctly? Am I headed down the wrong path? What I am looking for is a clean way to pull size limitations out of code and into configuration that I can access server side and in my templated angularJS/html.

Upvotes: 5

Views: 6621

Answers (3)

Bruno 82
Bruno 82

Reputation: 519

The bad news: there's no way to do what you want with standard annotations from Java Validation API.

The good news: you can easily create a custom annotation that does exactly what you want.

You need to create a custom validation annotation (let's call it @ConfigurableSize) that takes as parameters two strings, one for the name of the property holding the min size and one for the name of the property holding the max size.

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(ConfigurableSize.List.class)
@Constraint(validatedBy = {ConfigurableSizeCharSequenceValidator.class})
public @interface ConfigurableSize {

    String message() default "size is not valid";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String minProperty() default "";

    String maxProperty() default "";

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        ConfigurableSize[] value();
    }

}

The validator will retrieve the property values upon initialization, then it will perform the exact same validation checks as the @Size constraint. Even the constraint violation will have the exact same message. Please notice that if the property name is omitted the min and max will default respectively to 0 and Integer.MAX_VALUE, i.e. the same defaults for @Size.

public class ConfigurableSizeCharSequenceValidator implements ConstraintValidator<ConfigurableSize, CharSequence> {

    private final PropertyResolver propertyResolver;
    private int min;
    private int max;

    @Autowired
    public ConfigurableSizeCharSequenceValidator(PropertyResolver propertyResolver) {
        this.propertyResolver = propertyResolver;
    }

    @Override
    public void initialize(ConfigurableSize configurableSize) {
        String minProperty = configurableSize.minProperty();
        String maxProperty = configurableSize.maxProperty();
        this.min = "".equals(minProperty) ? 0 :
                propertyResolver.getRequiredProperty(minProperty, Integer.class);
        this.max = "".equals(maxProperty) ? Integer.MAX_VALUE :
                propertyResolver.getRequiredProperty(maxProperty, Integer.class);
        validateParameters();
    }

    private void validateParameters() {
        if (this.min < 0) {
            throw new IllegalArgumentException("The min parameter cannot be negative.");
        } else if (this.max < 0) {
            throw new IllegalArgumentException("The max parameter cannot be negative.");
        } else if (this.max < this.min) {
            throw new IllegalArgumentException("The length cannot be negative.");
        }
    }

    @Override
    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        } else {
            int length = value.length();
            boolean retVal = length >= this.min && length <= this.max;
            if (!retVal) {
                HibernateConstraintValidatorContext hibernateContext =
                        context.unwrap(HibernateConstraintValidatorContext.class);
                hibernateContext.addMessageParameter("min", this.min)
                        .addMessageParameter("max", this.max);
                hibernateContext.disableDefaultConstraintViolation();
                hibernateContext
                        .buildConstraintViolationWithTemplate("{javax.validation.constraints.Size.message}")
                        .addConstraintViolation();
            }
            return retVal;
        }
    }

}

You apply the custom annotation in your bean

public class Device {

    @ConfigurableSize(minProperty = "device.name.minsize", maxProperty = "device.name.maxsize")
    private String name;

}

Then finally in your application.properties you'll define the properties

device.name.minsize=4
device.name.maxsize=8

And that's it. You can find more details and a full example in this blog post: https://codemadeclear.com/index.php/2021/03/22/easily-configure-validators-via-properties-in-a-spring-boot-project/

Upvotes: 1

Lukas Hinsch
Lukas Hinsch

Reputation: 1860

For yet another approach take a look at https://github.com/jirutka/validator-spring. It allows you to use SpSEL expressions in bean validation annotations including config properties. You won't be able to use the standard annotations like @Size though, you'd have to formulate the constraints as SpEL expressions.

Upvotes: 1

ddc
ddc

Reputation: 83

I don't think you'll be able to do that. Annotations require constant values as their parameters, since they need to be handled at compile time.

You could externalize the xml: http://beanvalidation.org/1.1/spec/#xml-config

Alternatively, if you just want to use JSR-303 annotation metadata in AngularJS, you might have a look at Valdr and Valdr BeanValidation: https://github.com/netceteragroup/valdr https://github.com/netceteragroup/valdr-bean-validation

Upvotes: 1

Related Questions