Spring Boot validation: get max size from property file - java

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.

Related

Spring MVC validation and Thymeleaf - validating Integer field

I am moving .net project to Spring Boot.
So the question is on how to properly validate Integer fields in Spring.
I have an entity with an Integer field:
#Entity
#Table(name = "tb_employee")
public class EmployeeDev {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "empl_id")
private int emplId;
#Range(min = 10, max = 50, message="Numbers only between 10 and 50")
#Column(name = "default_vacation_days", nullable = true)
private Integer defaultVacationDays;
... and a controller capturing the errors:
// update employee
#PostMapping("/edit")
public String showFormForUpdate(#Valid #ModelAttribute("employee") EmployeeDev employee, Errors errors,
RedirectAttributes redirectAttributes,
Model theModel) {
if (null != errors && errors.getErrorCount() > 0) {
List<ObjectError> errs = errors.getAllErrors();
String errMsg = "";
for (ObjectError e :errs)
errMsg += e.getDefaultMessage();
theModel.addAttribute("message", "Employee Edit failed. " + errMsg );
theModel.addAttribute("alertClass", "alert-danger");
return "employeesdev/employee-form-edit";
}
Now the problem is when I type into the default vacation days field any number outside of the range
it shows the correct validation message: Numbers only between 10 and 50.
However if I try to insert something like 1A (possible user typo) I get this message:
Failed to convert property value of type java.lang.String to required type java.lang.Integer for property defaultVacationDays; nested exception is java.lang.NumberFormatException: For input string: "1A"
I understand this is the correct message but I hate to show a message like this to a user.
I would prefer to show just "Numbers only between 10 and 50" instead of data type conversion problems.
Why bother users with Java data types?
I would appreciate any suggestions.
If you want get custom behaviour from the annotation you need to define your own constriant annotation and validator for this annotation.
Here is basic example of custom constraint annotation:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = CheckCalculationTypeValidator.class)
#Documented
public #interface CheckCalculationType {
String message() default "calculation_type shall be not NULL if status = active";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and validator:
public class CheckCalculationTypeValidator implements ConstraintValidator<CheckCalculationType, RequestDto> {
#Override
public boolean isValid(RequestDto dto, ConstraintValidatorContext constraintValidatorContext) {
if (dto == null) {
return true;
}
return !(Status.ACTIVE.equals(dto.getStatus()) && dto.getCalculationType() == null);
}
#Override
public void initialize(CheckCalculationType constraintAnnotation) {
// NOP
}
}
Required dependency for Hibernate Validator:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>

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}}

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).

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.
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/

Categories

Resources