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;
}
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
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
public enum FuelTypeEnum {DIESEL, PETROL, ELECTRIC, HYBRID, ...};
public enum BodyTypeEnum {VAN, COUPE, MUV, JEEP, ...};
@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();
}
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+.