How to interpolate message dynamically in ConstraintValidator with property node? - java

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?

Related

Spring Boot ConstraintValidator problem with null #PathVariable

I created a custom validator in Spring Boot but when I pass the #PathVariable value to the validator, it gets the parameter name for null values.
#Validated // added for controller
#GetMapping("/employees/{code}")
public ResponseEntity<ApiResponse<List<RunwayBean>>> findByCode(
#ValidInput #PathVariable("code") String code) {
// ...
}
#Documented
#Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = {InputValidator.class})
public #interface ValidInput {
String message() default INVALID_INPUT;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And then in the validator below, str value is :code when I pass the parameter null on Postman. But when I add any string, it can be received correctly. So, how can I fix it?
public class InputValidator implements ConstraintValidator<ValidInput, String> {
#Override
public void initialize(ValidInput constraintAnnotation) {}
#Override
public boolean isValid(String str, ConstraintValidatorContext context) {
return str!= null && !str.isBlank();
// str = ":code" when #PathVariable value is null
}
}

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

Javax.validation - how to validate a Date attribute with a specifies date

I have been trying validate a date attribute with java.util.Date class be means of javax.validation annotations.
I have found some annotation to do it, for instance: #Past, #PastOrPresent, #Future, #FutureOrPresent; however I need the validation will be with a specific date.
I am searching something like this:
#Future("01/01/2019")
Does someone know how can I do it?. I would like to do it with annotation existing (I don't like to do it with custom annotations).
Regards.
I was able to create the annotation of this way:
FutureCustom annotation
Interface:
#Target({FIELD, PARAMETER})
#Retention(RUNTIME)
#Constraint(validatedBy = FutureCustom.class)
public #interface FutureCustom {
public abstract String max() default "";
public abstract String message() default "Message";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}
Class:
public class FutureCustomImpl implements ConstraintValidator<FutureCustom, Date> {
private String max;
#Override
public void initialize(FutureCustom constraintAnnotation) {
this.max = constraintAnnotation.max();
}
#Override
public boolean isValid(Date value, ConstraintValidatorContext context) {
//Validation using value and max like parameter in annotation
// The max attribute will always be a String, you have to convert it to Date.
}
}
Of this Way the annotation will be:
#FutureCustom(max="10/10/2010")

Is it possible to overload SpringBoot's ConstraintValidator isValid method?

Essentially, my service takes the role of streamlining the delivery of Email Notifications for standardization/control. I've therefore exposed a POST endpoint which takes in an email Bean as the response body which holds information such as receiver, sender, cc, etc and I would like to verify the fields of the incoming bean (i.e. email address format).
Currently, I have written a custom validator for validating a list of email addresses (#EmailAddresses). Is there a way to reuse the same validator to validate the email address for the "from" property which isn't a list as opposed to introducing another validator?
My Bean:
public class Email {
#JsonProperty("from")
private String from;
#EmailAddresses
#JsonProperty("to")
private List<String> to;
#EmailAddresses
#JsonProperty("cc")
private List<String> cc;
// some other fields
}
My Controller:
#RestController
public class MyController {
#RequestMapping(value = "/", method = RequestMethod.POST)
public String deliverEmailNotification(#Valid #RequestBody Email email) {
// something
}
}
My Custom Validation Annotation:
#Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = EmailAddressesValidator.class)
public #interface EmailAddresses {
String message() default "Must be a valid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validation Implementation:
public class EmailAddressesValidator implements
ConstraintValidator<EmailAddresses, List<String>> {
#Override
public void initialize(EmailAddresses emailAddresses) {
// do nothing
}
#Override
public boolean isValid(final List<String> emails, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
}
So essentially I'm wondering whether is it possible to do something like this:
public class EmailAddressesValidator implements
ConstraintValidator<EmailAddresses, List<String>>, ConstraintValidator<EmailAddresses, String> {
#Override
public void initialize(EmailAddresses emailAddresses) {
// do nothing
}
#Override
public boolean isValid(final List<String> emails, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
#Override
public boolean isValid(final String email, ConstraintValidatorContext constraintValidatorContext) {
// do something
}
}
Or is there another way around it?
Didn't manage to implement two instances of the ConstraintValidator due to Duplicate class error. However, I was able to achieve the equivalent of overloading by having the validation interface be validated by two implementation classes.
Based on the field type annotated with the validation annotation (#EmailAddress in this case), the respective validation implementation will kick in.
#Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = { EmailAddressValidator.class, EmailAddressesValidator.class })
public #interface EmailAddress {
String message() default "Must be a valid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validation implementation for List of Strings
public class EmailAddressesValidator implements ConstraintValidator<EmailAddress, List<String>> {
...
}
Validation implementation for String
public class EmailAddressValidator implements ConstraintValidator<EmailAddress, String> {
...
}

How to use an annotation element inside a custom constraint validator

I wrote a custom annotation in my project called CGC:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = CGCValidator.class)
public #interface CGC {
String message() default "{person.cgc.error}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
boolean canBeNull() default false;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
public #interface List {
CGC[] value();
}
}
I have a validator class that uses the annotation and basically, as my first validation I wanna check if the field is null, but only If the annotation for that field has specified the "canBeNull" element as true (#CGC(canBeNull="true")). My question is: how can I access the canBeNull element inside my validator class?
*The validator should be something like this:
public class CGCValidator implements ConstraintValidator<CGC, String> {
#Override
public void initialize(CGC annotation) {
}
#Override
public boolean isValid(String cgc, ConstraintValidatorContext constraintValidatorContext) {
if(!canBeNull() && cgc == null) {
return false;
}
...
You can capture the canBeNull value in the initialize function:
class CGCValidator implements ConstraintValidator<CGC, String> {
boolean canBeNull;
#Override
public void initialize(CGC constraintAnnotation) {
canBeNull = constraintAnnotation.canBeNull();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return canBeNull || value != null;
}
}

Categories