Using Spring #Value inside #Size - java

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.

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

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.

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/

Related

openapi 3.0 valid minimum and maximum values

I have below schema definition to represent commission amount in my openapi contract.
commissionAmount:
type: number
minimum: -99999.99
maximum: 99999.99
Generated Code:
#Valid
#DecimalMin("-99999.99") #DecimalMax("99999.99")
public BigDecimal getCommissionAmount() {
return commAmt;
}
The generated code is good and as expected. I just wanted to know are these -99999.99 and 99999.99 valid values for minimum and maximum.
The reason for asking this question is it does not check the limit in the fractional part. For example, I expect 12345.678 is invalid , 12345.67 is valid. But it marks both as valid.
I read #Digits is used to check for the digit limit of integer and fractional part. How do I tell openapi-generator-maven-plugin to annotate Digits as well?
Expected Generated Code:
#Valid
#Digits(integer = 5, fraction = 2)
#DecimalMin("-99999.99") #DecimalMax("99999.99")
public BigDecimal getCommissionAmount() {
return commAmt;
}
The way to specify this in OpenAPI would be using multipleOf:
commissionAmount:
type: number
minimum: -99999.99
maximum: 99999.99
multipleOf: 0.01
However, using the OpenAPI Generator will not produce an annotation for this. The reason being is that there is no javax.validation annotation that can represent multipleOf effectively (imagine trying to express multipleOf: 0.02- #Digits would be insufficient).
However, you can create your own annotation as this user has: https://github.com/OpenAPITools/openapi-generator/issues/2192#issuecomment-575132233
With the following annotation and validator:
#Target({METHOD, FIELD})
#Retention(RUNTIME)
#Repeatable(MultipleOf.List.class)
#Constraint(validatedBy = MultipleOfValidator.class)
public #interface MultipleOf {
double value();
String message() default "{error.multipleOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default { };
#Target({ METHOD, FIELD })
#Retention(RUNTIME)
#Documented
#interface List {
MultipleOf[] value();
}
}
public class MultipleOfValidator implements ConstraintValidator<MultipleOf, Number> {
private double value;
#Override
public void initialize(MultipleOf constraintAnnotation) {
this.value = constraintAnnotation.value();
}
#Override
public boolean isValid(Number value, ConstraintValidatorContext context) {
return (value == null) || (value.doubleValue() / this.value) % 1 == 0;
}
}
You would then be able to fork the generator and add your new annotation to the template: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/Java/beanValidationCore.mustache
With a line like this:
{{#multipleOf}}#MultipleOf({{multipleOf}}){{/multipleOf}}

Spring Boot validation: get max size from property file

I have got a spring boot server and would like to validate my values by spring. Using the #Size validation I can set the max size. But i would like to get this max size from my application.property file.
I have already tried to load this value by "#Value(...)" but I can not use this value in the "#Size" field.
#Value("${max.size.in.properties}")
private int MAX_SIZE;
#Size(max = 10)
private String description;
We can programmatically specify constraints using Hibernate Validator, which is already available in the classpath when using spring-boot-starter-web.
Given:
class MyObject {
private String description;
...
}
We can setup constraints like this:
#Value("${max.size.in.properties}")
private int MAX_SIZE;
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping.type( MyObject.class )
.property( "description", FIELD )
.constraint( new SizeDef().min( 1 ).max( MAX_SIZE ) );
and validate an instance of the object with:
Validator validator = configuration.addMapping( constraintMapping )
.buildValidatorFactory()
.getValidator();
Set<ConstraintViolation<MyObject>> constraintViolations =
validator.validate( myObjectInstance );
if (constraintViolations.size() > 0) {
... // handle constraint violations
}
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 SomeBean {
#ConfigurableSize(maxProperty = "max.size.in.properties")
private String description;
}
Then finally in your application.properties you'll define the property
max.size.in.properties=10
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/
you can do it like this post per java reflection https://www.baeldung.com/java-reflection-change-annotation-params
This is not possible as annotations require constant expressions (static final) and #Value cannot be used to inject values into static final fields.
Maybe this project might help you out: https://github.com/jirutka/validator-spring.
It allows you to use Spring Expression Language together with bean validation.

What is the method signature means? String value() default "";

I came across this method signature in Spring Component interface.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Indexed
public #interface Component
{
String value() default "";
}
What is the method signature String value() default ""; means?
How and When should we define such syntax for our coding purposes?
This is no method signature. it means that you can pass a String as parameter to the Component annotation, like this:
#Component(value = "value")
If you don't specify a value your self, the default value "" will be used.
If it had been like this:
String value(); // without the default
value would have been a mandatory parameter. Trying to use Component like this:
#Component()
would lead to an Exception, since you didn't provide a value.
EDIT: when to use.
If you don't know much about this syntax, or annotations in general, you shouldn't use them. About everything that can be done using annotations, especially custom made ones, can also be done without annotations.
Let's say you want to create an annotation to validate the value of a field.
I'll be using the example of Belgian postal codes. They all are 4 digits, and are between 1000 and 9999.
#Target( {ElementType.FIELD})
#Retention( RetentionPolicy.RUNTIME)
#Constraint( validatedBy = ValidatePostalCodeImpl.class)
public #interface ValidatePostalCode{
String message() default "You have entered an invalid postal code";
Class<?>[] groups() default {}; // needed for the validation
Class<? extends Payload>[] payload() default{}; // needed for the validation
int maxValue() default 9999; // so, by default, this will validate based
int minValue() default 1000; // on these values, but you will be able to
// override these
}
/* Validation implementation */
public class ValidatePostalCodeImpl implements ConstraintValidator<ValidatePostalCode, Integer> {
int upperValue;
int lowerValue;
#Override
public void initialize(ValidatePostalCode validate) {
this.upperValue = validate.maxValue(); // here you call them as if they indeed were regular methods
this.lowerValue = validate.minValue();
}
#Override
public boolean isValid(Integer integer, ConstraintValidatorContext context) {
return integer >= lowerValue && integer <= upperValue;
}
}
/* Usage */
#Entity
#Table(name = "addresses")
public class Addresses {
// home address -> In Belgium, so has to be between the default values:
#ValidatePostalCode
Integer belgianPostalCode;
// vacation home in another country, let's say the PC's there are between
// 12000 and 50000
#ValidatePostalCode(minValue = 12000, maxValue = 50000)
Integer foreignPostalCode;
}
Sure, this is a very limited example, but it should get you an idea.
The #interface keyword is used to define an annotation. This annotation has a property called value, which you could specify explicitly:
#Component(value = "myValue")
Or in the shorthand form:
#Component("myValue")
If you don't specify the value, it will default to "", as defined by the default keyword.

TYPE_USE annotation in hibernate validator

I know hibernate validator supports TYPE_USE annotations: though it does not define its own, it lets you define and use custom ones.
I could define and validate correctly such an annotation (code soon), but then I want to map the error into a path that is used to display the error to the user.
Given then following sample
public class SampleTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static class LimitedSizeStringValidator implements ConstraintValidator<LimitedSize, String> {
private LimitedSize constraint;
#Override
public void initialize(LimitedSize constraintAnnotation) {
this.constraint = constraintAnnotation;
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
String s = Ensure.notNull(value);
return s.length() >= constraint.min() &&
s.length() <= constraint.max();
}
}
#Retention(RUNTIME)
#Documented
#Target({TYPE_USE})
#Constraint(validatedBy = {LimitedSizeStringValidator.class})
public #interface LimitedSize {
String message() default "{javax.validation.constraints.Size.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
}
private static class TestBean {
#Valid
private Collection<#LimitedSize(max = 3) String> strings = new ArrayList<>();
#Valid
private Collection<InnerBean> beans = new ArrayList<>();
}
private static class InnerBean {
#Min(3)
private final int value;
private InnerBean(int value) {
this.value = value;
}
}
#Test
public void testBeanInvalid() {
TestBean testBean = new TestBean();
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("ok");
testBean.strings.add("ok2");
testBean.beans.add(new InnerBean(4));
assertThat(validator.validate(testBean)).isEmpty();
testBean.strings.add("not_ok");
testBean.beans.add(new InnerBean(2));
Set<ConstraintViolation<TestBean>> violations = validator.validate(testBean);
assertThat(violations).hasSize(2);
StreamSupport.stream(violations.spliterator(), false)
.forEach(v -> {
System.out.println(v.getPropertyPath());
System.out.println(v.getMessage());
v.getPropertyPath().forEach(p -> System.out.print("'" + p.getName() + (p.getIndex() != null ? "[" + p.getIndex() + "]" : "") + "' -> "));
System.out.println();
});
}
}
I would like map the errors in an object like
errors: [
["beans", "1", "value"],
["strings", "2"]
]
As in my sample, my approach at the moment is by navigating the violation path (http://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html#getPropertyPath--) which works perfectly for the first case, but fails for the second (I cannot find a way to retrieve the index of the failing object). I think the reason is in the implementation of javax.validation.Path.PropertyNode in hibernate-validator (I am currently on version 5.2.4.Final, and the code looks the same as in the linked 5.2.1.Final. For reference:
#Override
public final Integer getIndex() {
if ( parent == null ) {
return null;
}
else {
return parent.index;
}
}
With TYPE_USE this approach cannot work in my opinion, because the failing object is a leaf, thus no child node can retrieve the index from it.
Nice enough, hibernate implementation of javax.validation.Path overrides the toString method is way such that violation.getPropertyPath().toString() is beans[1].value and strings[2] (in the sample code above).
So, to the question(s): is my navigation approach wrong and there is another way to extract such a mapping from the ConstraintViolation? Or is this a feature request for hibernate developers (I can see that before TYPE_USE annotations the getIndex approach they implemented was totally fine?
It just feels strange I am the first one with this problem (I tried to google and could not find anything related, the closest being: https://github.com/hibernate/hibernate-validator/pull/441) so I am wondering whether the mistake is mine rather than a hibernate limitation
I agree that the index should be set for that value and think you uncovered an issue in Hibernate Validator. Could you open an issue in our JIRA tracker?
Btw. the notion of TYPE_USE level constraints will be standardized as of Bean Validation 2.0. So there may be some more changes coming up in this area, specifically I'm wondering what Kind that node should have (currently it's PROPERTY which seems questionable).

Creating custom annotations with place-holder?

I'm creating some custom annotations. I need to create someones with "place-holders" as it is used in Spring
#Value("#{aParameter}")
or in JSF 2
#ManagedProperty(value="#{aParameter}")
I suppose that I must have a mapping somewhere (.properties or .xml file or an enum class) but I need to know to code this approach in custom annotation interface. I mean how to declare a place-holder in the annoatation interface ? and how to ensure the assignement of its value (in mapping file) when applying the annotation somewhere?
Thanks in advance.
You don't do it in the annotation declaration - you do it in the code using that annotation.
For example the #Value is declared like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public #interface Value {
/**
* The actual value expression: e.g. "#{systemProperties.myProp}".
*/
String value();
}
and if you trace how it's used you'll see that in org.springframework.web.bind.annotation.support.HandlerMethodInvoker class the value is fetched directly from the annotation defaultValue = ((Value) paramAnn).value(); and then resolved like this:
if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
...
class AnnotationMethodHandlerAdapter{
...
protected Object resolveDefaultValue(String value) {
if (beanFactory == null) {
return value;
}
String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return value;
}
return exprResolver.evaluate(placeholdersResolved, expressionContext);
}
So the logic taking care of resolving properties and such is placed in classes
that actually use read annotations and make them useful.

Categories

Resources