I want to use standard javax.validation for properties and my own validation for functional validations. The simplified subclass looks like:
public class TestData extends AbstractData {
#NotNull
Long id = null;
#NotNull
Long value = null;
public Set<ConstraintViolation<TestData>> validateFunctional() {
Set<ConstraintViolation<TestData>> violations = new HashSet<>();
if ( id < 42 || value > 4711 ) {
//--- here comes another question: how do I create a constraint violation?
}
return violations;
}
}
This is the base class:
public abstract class AbstractData {
public Set<ConstraintViolation<?>> validate() {
//--- First validate single properties
Set<ConstraintViolation<?>> violations = validator.validate( this );
}
//--- Single props OK => validate functional
if ( violations.isEmpty()) {
violations.add( validateFunctional());
}
return violations;
}
Gives the error
Type mismatch: cannot convert from Set<ConstraintViolation<AbstractData>> to Set<ConstraintViolation<?>>
I believe this should be what you are looking for:
public abstract class AbstractData<T> {
abstract T getObj();
public Set<ConstraintViolation<T>> isValid(){
//do your custom validations if needed
return Validation.buildDefaultValidatorFactory()
.getValidator().validate(getObj());
}
}
POJO to be validated:
public class Bar extends AbstractData<Bar> {
#NotNull
private Long id;
#NotNull
private Long value;
#CustomConstraint
private Long customConstraint;
#Override
public Bar getObj() {
return this;
}
}
Then simply call bar.isValid().
EDIT:
Regarding your question about a custom constraint, you can do it like this:
#Constraint(validatedBy = {CustomConstraint.CustomConstraintValidator.class})
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(value = RetentionPolicy.RUNTIME)
#Documented
public #interface CustomConstraint {
String message() default "Invalid value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class CustomConstraintValidator implements ConstraintValidator<CustomConstraint, Long> {
#Override
public void initialize(CustomConstraint customConstraint) {
}
#Override
public boolean isValid(Long obj, ConstraintValidatorContext constraintValidatorContext) {
if (obj == null)
return false;
if (obj < 10)
return false;
return true;
}
}
}
In this example, if I annotate a Long field with #CustomConstraint and pass the value lower than 10 or null, the validator will return an error, otherwise it won't. The validation itself is useless in the example, I just put up something to serve as a snippet for you to build yours.
Related
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{
}
I have a DTO that looks something like this:
class VehicleDto {
private String type;
private Car car;
private Bike bike;
}
Now depending on the type, I need to validate on at least one of Car and Bike.
Both cannot be present in the same request.
How can I do that?
Having two fields in class, while only one of them can present, seems like a design smell for me.
But if you insist on such design - you can create a custom Validator for your VehicleDto class.
public class VehicleValidator implements Validator {
public boolean supports(Class clazz) {
return VehicleDto.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
VehicleDto dto = (VehicleDto) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "type",
"error.message.for.type.field");
if (null != dto.getType()
&& null != dto.getCar()
&& null != dto.getBike()) {
switch(dto.getType()) {
case "car":
errors.rejectValue("bike", "error.message.for.bike.field");
break;
case "bike":
errors.rejectValue("car", "error.message.for.car.field");
break;
}
}
}
}
Also, see Spring documentation about validation:
Validation using Spring’s Validator interface
Resolving codes to error messages
Injecting a Validator
For example, if we want to check whether my TaskDTO object is valid, by comparing its two attributes dueDate and repeatUntil , following are the steps to achieve it.
dependency in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO class:
#ValidTaskDTO
public class TaskDTO {
#FutureOrPresent
private ZonedDateTime dueDate;
#NotBlank(message = "Title cannot be null or blank")
private String title;
private String description;
#NotNull
private RecurrenceType recurrenceType;
#Future
private ZonedDateTime repeatUntil;
}
Custom Annotation:
#Constraint(validatedBy = {TaskDTOValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidTaskDTO {
String message() default "Due date should not be greater than or equal to Repeat Until Date.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint Validator:
public class TaskDTOValidator implements ConstraintValidator<ValidTaskDTO, TaskDTO> {
#Override
public void initialize(ValidTaskDTO constraintAnnotation) {
}
#Override
public boolean isValid(TaskDTO taskDTO, ConstraintValidatorContext constraintValidatorContext) {
if (taskDTO.getRecurrenceType() == RecurrenceType.NONE) {
return true;
}
return taskDTO.getRepeatUntil() != null && taskDTO.getDueDate().isBefore(taskDTO.getRepeatUntil());
}
}
Make sure that you have #Valid in front of RequestBody of a postmapping method in your RestController. Only then the validation will get invoked:
#PostMapping
public TaskReadDTO createTask(#Valid #RequestBody TaskDTO taskDTO) {
.....
}
I hope this helps. If you need a detailed explanation on steps, have a look at this video
A little greedy question here, hope this one could also help others who want to know more about annotation validation
I am currently studying Spring, and for now, I am planning to try out the customize annotated validation.
I have searched a lot and now I know there are mainly two kinds of validations, one is used for the controller, and the other is the annotation method using #Valid
So here's my scenario:
Suppose I have two or more fields which can be null when they are ALL NULL.
But only when one of those fields contains any value except an empty string, those fields are required to have input. And I had two ideas but didn't know how to implement them correctly.
Here's the Class Example:
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
private Date birthday;
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
//Getter and Setter
}
Suppose I want that the birthday and confirmBirthday field need to be both null or the oppose, I may want to annotate them using one annotation for each of them and looks like this:
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
#NotNullIf(fieldName="confirmBirthday")
private Date birthday;
#NotNullIf(fieldName="birthday")
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
//Getter and Setter
}
So i do need to create the validation Annotation like this:
#Documented
#Constraint(validatedBy = NotNullIfConstraintValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD, ElementType.FIELD })
public #interface NotNullIf {
String fieldName();
String message() default "{NotNullIf.message}";
Class<?>[] group() default {};
Class<? extends Payload>[] payload() default {};
}
And After that i will need to create the Validator itself:
public class NotNullIfConstraintValidator implements ConstraintValidator<NotNullIf, String>{
private String fieldName;
public void initialize(NotNullIf constraintAnnotation) {
fieldName = constraintAnnotation.fieldName();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value == null) {
return true;
};
//TODO Validation
return false;
}
}
So how can it be achievable?
For another idea using the same Class as an example which said that i want birthday, confirmBirthday and birthdayMessdage can only be null or the oppose at the same time.
I may require to use the class annotated validation this time for cross-field validation.
Here's how i suppose to annotate the class:
#NotNullIf(fieldName={"birthday", "confirmBirthday", "birthdayMessage"})
public class Subscriber {
//Those field same as the above one
}
So when one of that field is not null, the rest of them also needs to be entered on the client size.
Is it Possible?
I have read this article: How to access a field which is described in annotation property
But I still confusing on how the annotation validation works from those elements I listed above.
Maybe I need some detail explanation on that code or even worse I may need some basic concept inspection.
Please Help!
For this you can use a type level annotation only because a field level annotation has no access to other fields!
I did something similar to allow a choice validation (exactly one of a number of properties has to be not null). In your case the #AllOrNone annotation (or whatever name you prefer) would need an array of field names and you will get the whole object of the annotated type to the validator:
#Target(ElementType.TYPE)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = AllOrNoneValidator.class)
public #interface AllOrNone {
String[] value();
String message() default "{AllOrNone.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class AllOrNoneValidator implements ConstraintValidator<AllOrNone, Object> {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private String[] fields;
#Override
public void initialize(AllOrNone constraintAnnotation) {
fields = constraintAnnotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
long notNull = Stream.of(fields)
.map(field -> PARSER.parseExpression(field).getValue(value))
.filter(Objects::nonNull)
.count();
return notNull == 0 || notNull == fields.length;
}
}
(As you said you use Spring I used SpEL to allow even nested fields access)
Now you can annotate your Subscriber type:
#AllOrNone({"birthday", "confirmBirthday"})
public class Subscriber {
private String name;
private String email;
private Integer age;
private String phone;
private Gender gender;
private Date birthday;
private Date confirmBirthday;
private String birthdayMessage;
private Boolean receiveNewsletter;
}
Consider adding compile-time validation for the field names. For example, in #Arne answer the strings "birthday" and "confirmBirthday" are not guaranteed to match actual field names at compile time. If you want to add that functionality, here's an example from my code for a slightly different example that assumes there are exactly two fields. The purpose is to assert that two fields are ordered... For example, it could be used for "beginDate" and "endDate".
public class OrderedValidator extends AbstractProcessor implements ConstraintValidator<Ordered, Object>
{
private String field1;
private String field2;
private Messager messager;
public void initialize(Ordered constraintAnnotation)
{
this.field1 = constraintAnnotation.field1();
this.field2 = constraintAnnotation.field2();
}
#Override
public synchronized void init(ProcessingEnvironment processingEnv)
{
super.init(processingEnv);
messager = processingEnv.getMessager();
}
#SuppressWarnings("unchecked")
public boolean isValid(Object value, ConstraintValidatorContext context)
{
Object field1Value = new BeanWrapperImpl(value).getPropertyValue(field1);
Object field2Value = new BeanWrapperImpl(value).getPropertyValue(field2);
boolean valid = true;
if (field1Value != null && field2Value != null)
{
if (field1Value.getClass().equals(field2Value.getClass()))
{
valid = ((Comparable) field1Value).compareTo((Comparable) field2Value) <= 0;
}
}
return valid;
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
{
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Ordered.class))
{
if (annotatedElement.getKind() != ElementKind.CLASS)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with " + Ordered.class.getSimpleName());
return true;
}
TypeElement typeElement = (TypeElement) annotatedElement;
List<? extends Element> elements = typeElement.getEnclosedElements();
boolean field1Found = false;
boolean field2Found = false;
for (Element e : elements)
{
if (e.getKind() == ElementKind.FIELD && field1 != null && field1.equals(e.getSimpleName()))
{
field1Found = true;
}
else if (e.getKind() == ElementKind.FIELD && field2 != null && field2.equals(e.getSimpleName()))
{
field2Found = true;
}
}
if (field1 != null && !field1Found)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Could not find field named " + field1);
return true;
}
if (field2 != null && !field2Found)
{
messager.printMessage(Diagnostic.Kind.ERROR, "Could not find field named " + field2);
return true;
}
}
return false;
}
}
How can I use hibernate annotations to validate an enum member field?
The following does not work:
enum UserRole {
USER, ADMIN;
}
class User {
#NotBlank //HV000030: No validator could be found for type: UserRole.
UserRole userRole;
}
Note you can also create a validator to check a String is part of an enumeration.
public enum UserType { PERSON, COMPANY }
#NotNull
#StringEnumeration(enumClass = UserCivility.class)
private String title;
#Documented
#Constraint(validatedBy = StringEnumerationValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, CONSTRUCTOR })
#Retention(RUNTIME)
public #interface StringEnumeration {
String message() default "{com.xxx.bean.validation.constraints.StringEnumeration.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
}
public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {
private Set<String> AVAILABLE_ENUM_NAMES;
#Override
public void initialize(StringEnumeration stringEnumeration) {
Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
//Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);
Set<? extends Enum<?>> enumInstances = Sets.newHashSet(enumSelected.getEnumConstants());
AVAILABLE_ENUM_NAMES = FluentIterable
.from(enumInstances)
.transform(PrimitiveGuavaFunctions.ENUM_TO_NAME)
.toSet();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null ) {
return true;
} else {
return AVAILABLE_ENUM_NAMES.contains(value);
}
}
}
This is nice because you don't loose the information of the "wrong value". You can get a message like
The value "someBadUserType" is not a valid UserType. Valid UserType
values are: PERSON, COMPANY
Edit
For those who want a non-Guava version it should work with something like:
public class StringEnumerationValidator implements ConstraintValidator<StringEnumeration, String> {
private Set<String> AVAILABLE_ENUM_NAMES;
public static Set<String> getNamesSet(Class<? extends Enum<?>> e) {
Enum<?>[] enums = e.getEnumConstants();
String[] names = new String[enums.length];
for (int i = 0; i < enums.length; i++) {
names[i] = enums[i].name();
}
Set<String> mySet = new HashSet<String>(Arrays.asList(names));
return mySet;
}
#Override
public void initialize(StringEnumeration stringEnumeration) {
Class<? extends Enum<?>> enumSelected = stringEnumeration.enumClass();
AVAILABLE_ENUM_NAMES = getNamesSet(enumSelected);
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ( value == null ) {
return true;
} else {
return AVAILABLE_ENUM_NAMES.contains(value);
}
}
}
And to customize the error message and display the appropriate values, check this: https://stackoverflow.com/a/19833921/82609
#NotBlank
Validate that the annotated string is not null or empty. The difference to NotEmpty is that trailing whitespaces are getting ignored.
Where as UserRole is not String and an object Use #NotNull
The annotated element must not be null. Accepts any type.
I suppose a more closely related to Sebastien's answer above with fewer lines of code and makes use of EnumSet.allOf in the expense of a rawtypes warning
Enums setup
public enum FuelTypeEnum {DIESEL, PETROL, ELECTRIC, HYBRID, ...};
public enum BodyTypeEnum {VAN, COUPE, MUV, JEEP, ...};
Annotation setup
#Target(ElementType.FIELD) //METHOD, CONSTRUCTOR, etc.
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValidator.class)
public #interface ValidateEnum {
String message() default "{com.xxx.yyy.ValidateEnum.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> targetClassType();
}
Validator setup
public class EnumValidator implements ConstraintValidator<ValidateEnum, String> {
private Set<String> allowedValues;
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public void initialize(ValidateEnum targetEnum) {
Class<? extends Enum> enumSelected = targetEnum.targetClassType();
allowedValues = (Set<String>) EnumSet.allOf(enumSelected).stream().map(e -> ((Enum<? extends Enum<?>>) e).name())
.collect(Collectors.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || allowedValues.contains(value)? true : false;
}
}
Now go ahead and annotate your fields as follow
#ValidateEnum(targetClassType = FuelTypeEnum.class, message = "Please select ...."
private String fuelType;
#ValidateEnum(targetClassType = BodyTypeEnum.class, message = "Please select ...."
private String bodyType;
The above assumes you have the Hibernate Validator setup and working with default annotation.
Often times, attempting to convert to an enum is not just by name (which is the default behavior with valueOf method). For example, what if you have enums representing DayOfWeek and you want an integer to be converted to a DayOfWeek? To do that, I created the following annotation:
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
#Constraint(validatedBy = {ValidEnumValueValidator.class})
public #interface ValidEnumValue {
String message() default "invalidParam";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> value();
String enumMethod() default "name";
String stringMethod() default "toString";
}
public class ValidEnumValueValidator implements ConstraintValidator<ValidEnumValue, String> {
Class<? extends Enum<?>> enumClass;
String enumMethod;
String stringMethod;
#Override
public void initialize(ValidEnumValue annotation) {
this.enumClass = annotation.value();
this.enumMethod = annotation.enumMethod();
this.stringMethod = annotation.stringMethod();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Enum<?>[] enums = enumClass.getEnumConstants();
Method method = ReflectionUtils.findMethod(enumClass, enumMethod);
return Objects.nonNull(enums) && Arrays.stream(enums)
.map(en -> ReflectionUtils.invokeMethod(method, en))
.anyMatch(en -> {
Method m = ReflectionUtils.findMethod(String.class, stringMethod);
Object o = ReflectionUtils.invokeMethod(m, value);
return Objects.equals(o, en);
});
}
}
You'd use it as follows:
public enum TestEnum {
A("test");
TestEnum(String s) {
this.val = s;
}
private String val;
public String getValue() {
return this.val;
}
}
public static class Testee {
#ValidEnumValue(value = TestEnum.class, enumMethod = "getValue", stringMethod = "toLowerCase")
private String testEnum;
}
Above implementation uses ReflectionUtils from Spring framework and Java 8+.
Is it possible to do the following with JSR-303 constraint annotations?
Given the following bean definitions:
public class Table {
private List<TableEntry> entries;
#NoDuplicates(path = "key")
#Valid
public List<TableEntry> getEntries() {
return entries;
}
public void setEntries(List<TableEntry> entries) {
this.entries = entries;
}
}
public class TableEntry {
private String key, value;
#NotEmpty
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#NoDuplicates would ensure that duplicate keys within entries signals invalidations on the corresponding keys. I know it's possible to make this signal invalidation on entries itself, but I'm not sure what I want is possible.
Here's what I have so far:
#Constraint(validatedBy = { NoDuplicates.Validator.class })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface NoDuplicates {
String message() default "{com.example.NoDuplicates.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String path();
#Named
class Validator implements ConstraintValidator<NoDuplicates, List<?>> {
private String path, message;
#Override
public void initialize(NoDuplicates noDuplicates) {
path = noDuplicates.path();
message = noDuplicates.message();
}
#Override
public boolean isValid(List<?> elements, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
final Set<Integer> indicesOfDuplicates = indicesOfDuplicates(elements);
for (final int index : indicesOfDuplicates) {
reportDuplicationError(context, index);
}
return indicesOfDuplicates.isEmpty();
}
private Set<Integer> indicesOfDuplicates(List<?> elements) {
// implementation
}
private void reportDuplicationError(ConstraintValidatorContext context, int index) {
// What goes here?
}
}
}
My two main questions are:
What, if anything, goes into the body of reportDuplicationError to signal invalidation on the name of the relevant entry (i.e. relative path "[${index}].name")?
Given a solution to question 1, is there any way to prevent NoDuplicates from signaling invalidation if the name of the relevant entry is already invalidated (in this case through the #NotEmpty, but preferably generally)?
I realize both of these are possible through other validation methods, but I want to know whether it's possible through JSR-303 constraint annotations. Thank you.