Variable into Spring Boot class annotation - java

Is there a way to pass a variable from a property file to a class annotation?
#MaxPeriod(firstDateField = "startDateFrom", secondDateField = "startDateTo", maxPeriod = "${variable.from.property:31}")
public class ProcessReportFilter {
private LocalDate startDateFrom;
private LocalDate startDateTo;
}
upd: annotation is used in the request object at bean validation
#Target({ElementType.TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MaxPeriodValidator.class)
#Repeatable(MaxPeriod.List.class)
public #interface MaxPeriod {
String message() default "Period between {firstDateField} and {secondDateField} must not exceed {maxPeriod} day(-s)";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String firstDateField();
String secondDateField();
long maxPeriod();
#Target({ElementType.TYPE})
#Retention(RUNTIME)
#interface List {
MaxPeriod[] value();
}
}

Related

How can we use either of the validation in spring boot?

I have two variables in my bean and I want either name or mobile to be filled, they cant be both null at the same time.
#NotNull
private String name;
#NotNull
private String mobile;
How can I achieve that?
You need to write a custom annotation for this and use on class
#AtLeastOneNotEmpty(fields = {"name", "phone"})
public class User{
Custom Annotation Implementation
#Constraint(validatedBy = AtLeastOneNotEmptyValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface AtLeastOneNotEmpty {
String message() default "At least one cannot be null";
String[] fields();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And Validator of Custom Annotation
public class AtLeastOneNotEmptyValidator
implements ConstraintValidator<AtLeastOneNotEmpty, Object> {
private String[] fields;
public void initialize(AtLeastOneNotEmpty constraintAnnotation) {
this.fields = constraintAnnotation.fields();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<String> fieldValues = new ArrayList<String>();
for (String field : fields) {
Object propertyValue = new BeanWrapperImpl(value).getPropertyValue(field);
if (ObjectUtils.isEmpty(propertyValue)) {
fieldValues.add(null);
} else {
fieldValues.add(propertyValue.toString());
}
}
return fieldValues.stream().anyMatch(fieldValue -> fieldValue!= null);
}
}
you can create your own validation or annotation
try like this :
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotNullConfirmed {
String message() default "they can not be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and class that implement it:
public class FieldConfirmedValidator implements ConstraintValidator<NotNullConfirmed, Object>{
#Override
public boolean isValid(Object user, ConstraintValidatorContext context) {
String name = ((Your_bo)user).getName();
String phone = ((Your_bo)user).getPhone();
return !name.isEmpty() && !phone.isEmpty();
}
}
and add this annotation to your class
#NotNullConfirmed
public class User{
}

Class level validator not applicable to field in spring

I have a custom interface:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { MyCustomValidator.class })
#Documented
public #interface ValidData {
String message() default EMPTY;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and I have a class that - until now - had this validator attached:
#ValidData(groups = AfterDefaultGroup.class)
public class RecoverData {
private String data;
It works, but I need to move the validator from class level to field level. I tried this:
public class RecoverData {
#ValidData(groups = AfterDefaultGroup.class)
private String data;
but I'm getting compilation error here:
ValidData not applicable to field
How can I fix it?
The #Target annotation defines where this annotation can be applied.
You are restricting it to TYPE and ANNOTATION_TYPEright now which doesn't allow to use it on fields.
According to the documentation you have to use ElementType.FIELD

How to interpolate message dynamically in ConstraintValidator with property node?

Assume I have simple dto with one field and this dto is also annotated with custom validation annotation:
#CustomAnnotation
public class SimpleDto {
private String field;
}
// setters and getters omited
Custom annotation:
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = CustomValidator.class)
#Documented
public #interface CheckMondialRelayShopOrderWeight {
String message() default "{temp.key.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and finally validator itself:
public class CustomValidator implements
ConstraintValidator<CustomAnnotation, SimpleDto> {
#Override
public void initialize(CustomAnnotation constraintAnnotation) {}
#Override
public boolean isValid(SimpleDto value,
ConstraintValidatorContext context) {
HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
hibernateContext.addMessageParameter("dynamicValue", 130);
hibernateContext.buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate()).addPropertyNode("field").addConstraintViolation().disableDefaultConstraintViolation();
return false;
}
}
and in application.properties:
CustomAnnotation.simpleDto.field=Your dynamic value is {dynamicValue}
But this doesn't work, this works just fine if I put hibernateContext.buildConstraintViolationWithTemplate("Your dynamic value is {dynamicValue}").addConstraintViolation().disableDefaultConstraintViolation(); instead of hibernateContext.buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate()).addPropertyNode("field").addConstraintViolation().disableDefaultConstraintViolation();
I can't figure out how to use the interpolation with addPropertyNode. Any suggestions?

Java annotation for Null but neither Empty nor Blank

Is there are any java annotation(s) that can validate like the example below?
String test;
test = null; //valid
test = ""; //invalid
test = " "; //invalid
test = "Some values"; //valid
You need to create a custom annotation: #NullOrNotBlank
First create the custom annotation: NullOrNotBlank.java
#Target( {ElementType.FIELD})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NullOrNotBlankValidator.class)
public #interface NullOrNotBlank {
String message() default "{javax.validation.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
Then the actual validator: NullOrNotBlankValidator.java
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, String> {
public void initialize(NullOrNotBlank parameters) {
// Nothing to do here
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return value == null || value.trim().length() > 0;
}
}
There isn't such an annotation in either javax.validation or Hibernate Validator. There was a request to add one to Hibernate Validator but it was closed as "won't fix" due to the possibility of writing your own relatively easily. The suggest solution was to either use your own annotation type defined like this:
#ConstraintComposition(OR)
#Null
#NotBlank
#ReportAsSingleViolation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface NullOrNotBlank {
String message() default "{org.hibernate.validator.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
or to use the #Pattern annotation with a regular expression that requires a non-whitespace character to be present (as the Pattern annotation accepts nulls and does not match them against the pattern).
Where is a nice javax.validation.constraints.Pattern annotation.
You can annotate the field with:
#Pattern(regexp = "^(?!\\s*$).+", message = "must not be blank")
This checks if field matches regex. The regex itself is something but not blank (see details here). It uses negative lookahead.
This is possible without creating a custom annotation, by using javax.validation.constraints.Size
// Null values are considered valid
#Size(min=1) String test;
The best way is to create your own constraint validator,
//custom annotation
#Documented
#Constraint(validatedBy = CustomCheck.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomConstarint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//validation logic goes here
public class CustomCheck implements
ConstraintValidator<CustomConstarint, String> {
#Override
public void initialize(CustomConstarint customConstarint) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
//write your logic to validate the field
}
}
Did you try Hibernate-Validator? I think that's what you are looking for.
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
public class MyModel {
#NotNull
private String str1;
#NotEmpty
private String str2;
#NotBlank
private String str3;
}

JSR303 Composite Annotation

I've created a composite annotation that consists of #Digits and #Min
#Digits(integer=12, fraction=0)
#Min(value=0)
#ReportAsSingleViolation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target( { FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Constraint(validatedBy={})
public #interface PositiveInt {
String message() default "{positive.int.msg}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
my problem is, I want to reuse this annotation where I want the #Digits 'integer' value to be specify when the PositiveInteger is use
example
public Demo{
#PositiveInteger(integer=1)
private Integer num1;
#PositiveInteger(integer=2)
private Integer num2;
}
where num1 can be 1-9, and num2 can be 1-99.
Is this even possible, if so, how do I go about this?
Currently, I have to provides a custom ConstraintValidator where i would have my validation code for the #Digits and #Min
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target( { FIELD, ANNOTATION_TYPE, PARAMETER })
#Constraint(validatedBy=PositiveIntConstraintValidator.class)
public #interface PositiveInt {
String message() default "positive.int.msg";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int integer() default 1;
}
public class PositiveIntConstraintValidator implements ConstraintValidator<PositiveInt, Number> {
private int maxDigits;
#Override
public void initialize(PositiveInt constraintAnnotation) {
maxDigits = constraintAnnotation.integer();
if (maxDigits < 1){
throw new IllegalArgumentException("Invalid max size. Max size must be a positive integer greater than 1");
}
}
#Override
public boolean isValid(Number value, ConstraintValidatorContext context) {
if (value == null){
return true;
}
else if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof BigInteger){
String regex = "\\d{"0," + maxDigits + "}";
return Pattern.matches(regex, value.toString());
}
return false;
}
You could make use of #OverridesAnnotation:
#Digits(integer=0, fraction=0)
#Min(value=0)
#ReportAsSingleViolation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target( { FIELD, METHOD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Constraint(validatedBy={})
public #interface PositiveInteger {
String message() default "{positive.int.msg}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#OverridesAttribute(constraint=Digits.class, name="integer")
int digits();
}
That way the value given in #PositiveInteger#digits() will be propagated to #Digits.

Categories