Can we add parameters to Json validation annonations? - java

I am doing the json field validation as below :
#Valid
#NotEmpty(message="Id must not be empty")
#Size(min=1, max=70, message="Id accepts max length of 70")
private String Id;
#Valid
#NotEmpty(message="application must not be empty")
#Size(min=1, max=8, message="application accepts max length of 8")
private String application;
This is working fine
but now i have got another requirement where while validating the second field i need to pass the id as well to the message, stating this application message is coming for which id
Example : application must not be empty for id = 123
or
application accepts max length of 8 for id = 456
I am not sure if i can pass other attribute from model to the message
If you have any clue please comment

To my knowledge, it is only possible to get current validated value, and the values in the annotation methods(min, max, etc). If someone actually knows of a way, please feel free to correct me.
To achieve access to another property value, you would need custom validation annotation on the type level. I'll assume the class holding data is called MyModel. Annotation might look like this:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyModelApplicationSizeValidator.class)
public #interface MyModelApplicationSize {
String message() default "default message";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int min() default 0;
int max() default Integer.MAX_VALUE;
}
And the actual validator for this annotation:
import com.example.random.model.MyModel;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MyModelApplicationSizeValidator implements ConstraintValidator<MyModelApplicationSize, MyModel> {
private int min;
private int max;
private String msgFormat;
#Override
public void initialize(MyModelApplicationSize constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
this.msgFormat = constraintAnnotation.message();
}
#Override
public boolean isValid(MyModel model, ConstraintValidatorContext context) {
String value = model.getApplication();
if (value == null || value.isEmpty() || value.length() < this.min || value.length() > this.max) {
context.disableDefaultConstraintViolation();
String message = String.format(this.msgFormat, this.min, this.max, value, model.getId());
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
return true;
}
}
Keep in mind building error message with String.format is not the best way to do it, it's kind of brittle, you might actually need to implement it in another way, depending on your needs.
And MyModel, validated with the new annotation:
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
#MyModelApplicationSize(message = "application accepts min length of %d and max length of %d for val - %s with id - %s", min = 2, max = 7)
public class MyModel {
#NotEmpty(message="Id must not be empty")
#Size(min=1, max=70, message="Id accepts max length of 70")
private String id;
#NotEmpty(message="application must not be empty")
#Size(min=1, max=8, message="application accepts min length of {min} and max length of {max} for val - ${validatedValue}")
private String application;
//getters and setters
}

Related

How to validate whethere a username is an email or phone number in springboot application

I am quite new to springboot and I am trying to create validate whether a username field is a phone number or email address in my registerDTO.
Here is how the registerDTO looks like:
import lombok.Data;
import lombok.Data;
import javax.validation.constraints.*;
#Data
public class RegisterDTO {
#NotNull(message= "This field cannot be blank")
private String firstName;
#NotNull(message="This field cannot be blank")
private String lastName;
private String username;
#Digits(integer = 4, fraction = 0)
#Pattern(regexp ="^[0-9]{4}$")
#Size(min=4, max=4, message = "Password can only be 4 digits")
private Integer password;
I want to write a custom regex that will validate if the username instance is either an email or phone number. The phone number should start with zero and not exceed 11 (maximum_length). It should throw an error if it's neither of the two.
It is a little old but you can try to use the #ConstraintComposition to create your custom annotation which would be a composition of other constraint annotations.
#ConstraintComposition(CompositionType.OR)
#Email
#Pattern(regexp = "^0\\d{11}")
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {})
#ReportAsSingleViolation
#Documented
public #interface EmailOrPhone {
String message() default "Provided value was neither a valid Email nor a valid Phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default { };
}
Then you just use your custom annotation
public class RegisterDTO {
#EmailOrPhone
private String username;
.....

javax validation greater or less than from other property

with respect to javax.validation
#NotNull(message = "From can't be null")
#Min(value = 1, message = "From must be greater than zero")
private Long from;
#NotNull(message = "To can't be null")
#Min(value = 1, message = "To must be greater than zero")
private Long to;
I want to also validate that FROM should be less than TO and TO should be greater than FROM ? how we can do this using javax validation's annotation ?
You need a custom cross field validation annotation.
One way is to annotate your custom class with #YourCustomAnnotation.
In YourCustomAnnotationValidator you have access to your value, hence you can implement your logic there:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Constraint(validatedBy = DateValidator.class)
public #interface RangeCheck {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class RangeCheckValidtor implements ConstraintValidator<RangeCheck, YourDto> {
#Override
public void initialize(RangeCheck date) {
// Nothing here
}
#Override
public boolean isValid(YourDto dto, ConstraintValidatorContext constraintValidatorContext) {
if (dto.getFrom() == null || dto.getTo() == null) {
return true;
}
return from < to;
}
}
Then mark your YourDto class with #RangeCheck:
#RangeCheck(message = "your messgae")
public class YourDto {
// from
// to
}
Or simply manually validate the relation of two fields.

How insert field name when using internationalization with javax validations?

I'm using javax validation annotations with Spring Boot and internationalization. So I have the following field:
#Size(min = 3, max = 3, message = "{javax.validation.constraints.Size.message}")
private String currencyCode;
The US resource bundle has:
javax.validation.constraints.Size.message = size must be between {min} and {max}
So when this field fails on validation, I see the message "size must be between 3 and 3".
But what I want is to have a message like:
javax.validation.constraints.Size.message = {fieldName}'s size must be between {min} and {max}
Which would result in the message "currencyCode's size must be between 3 and 3".
Is this possible? Do I need to override a bean to make it work? Is there a pre-defined property for the field name?
You can create handler for MethodArgumentNotValidException
#ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) { ...
inside them you can invoke
e.getBindingResult().getFieldErrors();
This is collection with all informations about invalid field error eg. field name, rejected value.
At next, use message like in your's example and use StrSubstitutor from org.apache.commons.lang3. Just create map of parameters
key --> fieldName , value --> currencyCode
Of course you have to write some code, but here is the solution :)
By default #Size annotation does not provide option to put label in the message, instead you can create your own implementation like below so that it can be used across your application:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = MySizeValidator.class)
public #interface MySize {
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;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#interface List {
Size[] value();
}
public abstract String label();
}
then create your own validator like MySizeValidator to validate MySize annotation like below
public class MySizeValidator implements ConstraintValidator<MySize, String> {
private static final Log log = LoggerFactory.make();
private int min;
private int max;
#Override
public void initialize(MySize parameters) {
min = parameters.min();
max = parameters.max();
validateParameters();
}
#Override
public boolean isValid(String field, ConstraintValidatorContext constraintValidatorContext) {
if (field == null) {
return true;
}
int length = field.length();
return length >= min && length <= max;
}
private void validateParameters() {
if (min < 0) {
throw log.getMinCannotBeNegativeException();
}
if (max < 0) {
throw log.getMaxCannotBeNegativeException();
}
if (max < min) {
throw log.getLengthCannotBeNegativeException();
}
}
}
Create ValidationMessages.properties with below key value
javax.validation.constraints.mySize.message={label}'s size must be between {min} and {max}
then in your POJO, you can put this new annotation,
#MySize(min = 3, max = 3, message = "{javax.validation.constraints.mySize.message}", label = "Currency Code")
private String currencyCode;

Java Annotation at runtime

the following class exists which consist from predefined UUID's that describe possible entires of the database.
public class Predefined {
#NotNull
#Size(min = 1, max = 25)
public UUID phone = UUID.fromString("47b58767-c0ad-43fe-8e87-c7dae489a4f0");
#NotNull
#Size(min = 1, max = 40)
public UUID company = UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2");
}
Those values are received as a key pair value trough web service: and then they are put to a hashmap.
47b58767-c0ad-43fe-8e87-c7dae489a4f0 = +00112233445566778899
f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2 = someVirtualCompnayName
When i receive an UUID that i know i am creating an instance of the Predefined class and then getting the annotations of the filed in the Predefined class i.e.:
Annotation[] annon = field.getDeclaredAnnotations();
Now I need to check those annotation agains the values that I got from the web services i.e. “+00112233445566778899” and “someVirtualCompnayName” at runtime
Is this possible?
I am especially interesting in example covering JSR 303.
Shortly why I have such construct:
The DAO , #Repository classes have different structure i.e.
contact
contact_attrbute
contact_attibute_type
where the databse “contact_attibute_type” is meant for “company” and “phone”. The second table i.e. “contact_attrbute” is meant for the actual values of “company” and “phone”.
Now I need a way to validate those values before I write them in hibernate, thus I am getting the “public UUID phone” and then trying to apply those constrains to the actual value I got from the user i.e. “+00112233445566778899”.
I'll post the complete code I have come up with to validate your test-case (including a simple executable demo):
Annotations:
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target( {ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface NotNull
{
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target( {ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Size
{
int min() default 0;
int max();
}
The Predefined class:
public class Predefined
{
#NotNull
#Size(min = 1, max = 25)
public UUID phone;
#NotNull
#Size(min = 1, max = 40)
public UUID company;
public Predefined(UUID phone, UUID company)
{
this.phone = phone;
this.company = company;
}
}
The validator class which iterates through the declared fields and checks their annotation and field/value mappings:
public class PredefinedValidator
{
public boolean validate(Predefined predefined, Map<UUID, String> mappings)
{
if (predefined == null)
return false;
for (Field field :predefined.getClass().getDeclaredFields())
{
if (field.getType().equals(UUID.class))
{
try
{
Annotation[] annotations = field.getDeclaredAnnotations();
UUID uuid = (UUID)field.get(predefined);
if (!this.validateField(uuid, annotations, mappings))
return false;
}
catch (IllegalArgumentException | IllegalAccessException ex)
{
Logger.getLogger(PredefinedValidator.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return true;
}
private boolean validateField(UUID field, Annotation[] annotations, Map<UUID, String> mapping)
{
boolean containsSize = false;
boolean containsNotNull = false;
int minSize = -1;
int maxSize = -1;
// fetch which annotations are available for the provided field
for (Annotation annotation : annotations)
{
if (annotation instanceof Size)
{
containsSize = true;
Size size = (Size)annotation;
minSize = size.min();
maxSize = size.max();
}
else if (annotation instanceof NotNull)
containsNotNull = true;
}
// check if the provided value is null and an annotatition for #NotNull
// is set
if (field == null && containsNotNull)
return false;
if (containsSize)
{
// get the value of the mapped UUID which we are going to validate
String value = mapping.get(field);
if (value == null && containsNotNull)
return false;
else if (value == null)
return true;
// check if the length of the value matches
if (value.length() <= minSize || value.length() >= maxSize)
return false;
}
// passed all tests
return true;
}
}
Last but not least a simple demo:
public static void main(String ... args)
{
Map<UUID, String> mappings = new HashMap<>();
mappings.put(UUID.fromString("47b58767-c0ad-43fe-8e87-c7dae489a4f0"), "+00112233445566778899");
mappings.put(UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2"), "someVirtualCompnayName");
Predefined predefined = new Predefined(
UUID.fromString("47b58767-c0ad-43fe-8e87-c7dae489a4f0"),
UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2"));
Predefined predefined2 = new Predefined(
UUID.randomUUID(),
UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2"));
Predefined predefined3 = new Predefined(
null,
UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2"));
PredefinedValidator validator = new PredefinedValidator();
System.out.println("predefined is valid: "+validator.validate(predefined, mappings));
System.out.println("predefined is valid: "+validator.validate(predefined2, mappings));
System.out.println("predefined is valid: "+validator.validate(predefined3, mappings));
mappings.put(UUID.fromString("f9a1e8f4-b8c0-41f2-a626-49c11da8d5c2"), "someVirtualCompnayNamesomeVirtualCompnayNamesomeVirtualCompnayNamesomeVirtualCompnayName");
System.out.println("predefined is valid: "+validator.validate(predefined, mappings));
}
HTH

Hibernate validation annotation - validate that at least one field is not null

Is there a way to define a Hibernate validation rule using annotations as defined here, stating that at least one field shall be not null?
This would be a hypothetical example (#OneFieldMustBeNotNullConstraint does not really exist):
#Entity
#OneFieldMustBeNotNullConstraint(list={fieldA,fieldB})
public class Card {
#Id
#GeneratedValue
private Integer card_id;
#Column(nullable = true)
private Long fieldA;
#Column(nullable = true)
private Long fieldB;
}
In the illustrated case, fieldA can be null or fieldB can be null, but not both.
One way would be to create my own validator, but I'd like to avoid if it already exists. Please share one validator if you have one already made... thanks!
I finally wrote the whole validator:
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import org.apache.commons.beanutils.PropertyUtils;
#Target( { TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckAtLeastOneNotNull.CheckAtLeastOneNotNullValidator.class)
#Documented
public #interface CheckAtLeastOneNotNull {
String message() default "{com.xxx.constraints.checkatleastnotnull}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fieldNames();
public static class CheckAtLeastOneNotNullValidator implements ConstraintValidator<CheckAtLeastOneNotNull, Object> {
private String[] fieldNames;
public void initialize(CheckAtLeastOneNotNull constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
if (object == null) {
return true;
}
try {
for (String fieldName:fieldNames){
Object property = PropertyUtils.getProperty(object, fieldName);
if (property != null) return true;
}
return false;
} catch (Exception e) {
return false;
}
}
}
}
Example of usage:
#Entity
#CheckAtLeastOneNotNull(fieldNames={"fieldA","fieldB"})
public class Reward {
#Id
#GeneratedValue
private Integer id;
private Integer fieldA;
private Integer fieldB;
[...] // accessors, other fields, etc.
}
Just write your own validator. Is't should be pretty simple: iterate over field names and get field values by using reflection.
Concept:
Collection<String> values = Arrays.asList(
BeanUtils.getProperty(obj, fieldA),
BeanUtils.getProperty(obj, fieldB),
);
return CollectionUtils.exists(values, PredicateUtils.notNullPredicate());
There I used methods from commons-beanutils and commons-collections.
This is a bit like Resh32's answer but this will also bind the validation message with a specific field of the validated object.
The validation annotation class will be like the following one.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* #author rumman
* #since 9/23/19
*/
#Target(TYPE)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NotNullAnyValidator.class)
public #interface NotNullAny {
String[] fieldNames();
String errorOnProperty();
String messageKey() default "{error.required}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator class will be like the following.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Objects;
import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
/**
* #author rumman
* #since 9/23/19
*/
public class NotNullAnyValidator implements ConstraintValidator<NotNullAny, Object> {
private String[] fieldNames;
private String errorOnProperty;
private String messageKey;
#Override
public void initialize(NotNullAny validateDateRange) {
fieldNames = validateDateRange.fieldNames();
errorOnProperty = validateDateRange.errorOnProperty();
messageKey = validateDateRange.messageKey();
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext validatorContext) {
Object[] fieldValues = new Object[fieldNames.length];
try {
for (int i = 0; i < fieldValues.length; i++) {
fieldValues[i] = getPropertyDescriptor(obj.getClass(), fieldNames[i]).getReadMethod().invoke(obj);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
if (Arrays.stream(fieldValues).noneMatch(Objects::nonNull)) {
validatorContext.buildConstraintViolationWithTemplate(messageKey)
.addPropertyNode(errorOnProperty)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
return true;
}
}
Pay attention to the last if condition block, this checks if no non null value is found then specifies the error message, the property with which the error message will be bound to and will add the constraint violation.
To use the annotation in a class
/**
* #author rumman
* #since 9/23/19
*/
#NotNullAny(fieldNames = {"field1", "field2", "field3"},
errorOnProperty = "field1",
messageKey = "my.error.msg.key")
public class TestEntityForValidation {
private String field1;
private String field2;
private String field3;
// standard constructor(s) and getter & setters below
}
If you could use javax.validation then I would use #AssertTrue.
Using javax.validation shouldn't be a problem since hibernate-validation is an implementation of javax.validation's interfaces/annotations which itself is an "implementation" of the Bean Validation 2.0 specification
#Entity
#OneFieldMustBeNotNullConstraint(list={fieldA,fieldB})
public class Card {
#Id
#GeneratedValue
private Integer card_id;
#Column(nullable = true)
private Long fieldA;
#Column(nullable = true)
private Long fieldB;
#AssertTrue(message = "at least one should be non-null")
public boolean isValid1() {
return Objects.nonNull(fieldA) || Objects.nonNull(fieldB);
}
}

Categories