How to use Hibernate validation annotations with enums? - java

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

Related

How to return all valid enum values to API client as JSON when using custom validation annotation

I need to return to API client list of valid DecisionStates when the client passes invalid string that cannot be mapped to DecisionStates enum.
Annotataion definition:
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = ValueOfEnumValidator.class)
public #interface ValueOfEnum {
Class<? extends Enum<?>> enumClass();
String message() default "must be any of enum {enumClass}: [" + Arrays.toString(enumClass().getEnumConstants()).replaceAll("^.|.$", "") + "]"; // <-- not compiling
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DTO:
#ValueOfEnum(enumClass = DecisionState.class)
String state;
ConstraintValidator implementation:
public class ValueOfEnumValidator implements ConstraintValidator<ValueOfEnum, CharSequence> {
private List<String> acceptedValues;
#Override
public void initialize(ValueOfEnum annotation) {
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
#Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}

Allow to override/set constraints validation for a child object for Hibernate validation

I have the following class :
class ContactInformation {
String phone;
String email;
}
which is used in the following classes :
class Person {
#Valid
ContactInformation contactInformation;
}
class Station {
#Valid
ContactInformation contactInformation;
}
The thing is that any instance of Person must have an email, but it is an optional information for Station. Do I have a way to define this at owner level to avoid duplicate the class ContactInformation ?
Instead of the field level validator you can add the Type level validator.
Steps:
Define Type level annotation
Write Validator for new annotation
Introduce your type with a new annotation
Define:
#Constraint(validatedBy = {PersonClassOptionalEmailValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface PersonalEmailValid {
String message() default "Invalid Email address";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Writing Custom Validator:
public static class PersonClassOptionalEmailValidator implements ConstraintValidator<PersonalEmailValid, Person> {
// Test Email validator, you should check prope regex for production
public static final String EMAIL_REGEX = "^[a-zA-Z0-9+_.-]+#[a-zA-Z0-9.-]+$";
private final Pattern pattern;
public PersonClassOptionalEmailValidator() {
pattern = Pattern.compile(EMAIL_REGEX);
}
#Override
public boolean isValid(Person person, ConstraintValidatorContext constraintValidatorContext) {
if (person.contactInformation != null) {
return pattern.matcher(person.contactInformation.email).matches();
}
return false;
}
}
Introduce new annotation to class
#Getter
#Setter
#NoArgsConstructor
#PersonalEmailValid
static class Person {
#Valid
ContactInformation contactInformation;
}
Reference
Gist

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

How do I use javax.validation (JSR303) on a base class?

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.

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