Merge pull request #7949 from martinvw/feature/BAEL-3306-validations-for-enum-types

[BAEL-3306] Validations for Enum types
This commit is contained in:
Jonathan Cook 2019-10-06 12:09:45 +02:00 committed by GitHub
commit 7cbe9ac12c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 558 additions and 0 deletions

View File

@ -9,3 +9,4 @@ This module contains articles about Bean Validation.
- [Difference Between @NotNull, @NotEmpty, and @NotBlank Constraints in Bean Validation](https://www.baeldung.com/java-bean-validation-not-null-empty-blank)
- [Javax BigDecimal Validation](https://www.baeldung.com/javax-bigdecimal-validation)
- [Grouping Javax Validation Constraints](https://www.baeldung.com/javax-validation-groups)
- [Javax Validations for Enums](https://www.baeldung.com/javax-validations-for-enums/)

View File

@ -0,0 +1,24 @@
package org.baeldung.javaxval.enums;
import java.util.Arrays;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.baeldung.javaxval.enums.constraints.CustomerTypeSubset;
import org.baeldung.javaxval.enums.demo.CustomerType;
public class CustomerTypeSubSetValidator implements ConstraintValidator<CustomerTypeSubset, CustomerType> {
private CustomerType[] subset;
@Override
public void initialize(CustomerTypeSubset constraint) {
this.subset = constraint.anyOf();
}
@Override
public boolean isValid(CustomerType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset)
.contains(value);
}
}

View File

@ -0,0 +1,33 @@
package org.baeldung.javaxval.enums;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.baeldung.javaxval.enums.constraints.EnumNamePattern;
public class EnumNamePatternValidator implements ConstraintValidator<EnumNamePattern, Enum<?>> {
private Pattern pattern;
@Override
public void initialize(EnumNamePattern constraintAnnotation) {
try {
pattern = Pattern.compile(constraintAnnotation.regexp());
} catch (PatternSyntaxException e) {
throw new IllegalArgumentException("Given regex is invalid", e);
}
}
@Override
public boolean isValid(Enum<?> value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Matcher m = pattern.matcher(value.name());
return m.matches();
}
}

View File

@ -0,0 +1,25 @@
package org.baeldung.javaxval.enums;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public abstract class EnumSubSetValidator<T extends Annotation, U> implements ConstraintValidator<T, U> {
private U[] subset;
protected void initialize(U[] subset) {
this.subset = subset;
}
@Override
public boolean isValid(U value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return Arrays.asList(subset)
.contains(value);
}
}

View File

@ -0,0 +1,11 @@
package org.baeldung.javaxval.enums;
import org.baeldung.javaxval.enums.constraints.CustomerTypeSubset;
import org.baeldung.javaxval.enums.demo.CustomerType;
public class InheritedCustomerTypeSubSetValidator extends EnumSubSetValidator<CustomerTypeSubset, CustomerType> {
@Override
public void initialize(CustomerTypeSubset constraint) {
super.initialize(constraint.anyOf());
}
}

View File

@ -0,0 +1,31 @@
package org.baeldung.javaxval.enums;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.baeldung.javaxval.enums.constraints.ValueOfEnum;
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());
}
}

View File

@ -0,0 +1,45 @@
package org.baeldung.javaxval.enums.constraints;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
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.Payload;
import org.baeldung.javaxval.enums.CustomerTypeSubSetValidator;
import org.baeldung.javaxval.enums.demo.CustomerType;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = CustomerTypeSubSetValidator.class)
public @interface CustomerTypeSubset {
/**
* @return subset of CustomerType enum
*/
CustomerType[] anyOf();
/**
* @return the error message template
*/
String message() default "must be any of {anyOf}";
/**
* @return the groups the constraint belongs to
*/
Class<?>[] groups() default {};
/**
* @return the payload associated to the constraint
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,45 @@
package org.baeldung.javaxval.enums.constraints;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
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.Payload;
import org.baeldung.javaxval.enums.EnumNamePatternValidator;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = EnumNamePatternValidator.class)
public @interface EnumNamePattern {
/**
* @return the regular expression to match
*/
String regexp();
/**
* @return the error message template
*/
String message() default "must match \"{regexp}\"";
/**
* @return the groups the constraint belongs to
*/
Class<?>[] groups() default {};
/**
* @return the payload associated to the constraint
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,44 @@
package org.baeldung.javaxval.enums.constraints;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
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.Payload;
import org.baeldung.javaxval.enums.ValueOfEnumValidator;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = ValueOfEnumValidator.class)
public @interface ValueOfEnum {
/**
* @return class containing enum values to which this String should match
*/
Class<? extends Enum<?>> enumClass();
/**
* @return the error message template
*/
String message() default "must be any of enum {enumClass}";
/**
* @return the groups the constraint belongs to
*/
Class<?>[] groups() default {};
/**
* @return the payload associated to the constraint
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,77 @@
package org.baeldung.javaxval.enums.demo;
import javax.validation.constraints.NotNull;
import org.baeldung.javaxval.enums.constraints.CustomerTypeSubset;
import org.baeldung.javaxval.enums.constraints.EnumNamePattern;
import org.baeldung.javaxval.enums.constraints.ValueOfEnum;
public class Customer {
@ValueOfEnum(enumClass = CustomerType.class)
private String customerTypeString;
@NotNull
@CustomerTypeSubset(anyOf = { CustomerType.NEW, CustomerType.OLD })
private CustomerType customerTypeOfSubset;
@EnumNamePattern(regexp = "NEW|DEFAULT")
private CustomerType customerTypeMatchesPattern;
public Customer() {
}
public Customer(String customerTypeString, CustomerType customerTypeOfSubset, CustomerType customerTypeMatchesPattern) {
this.customerTypeString = customerTypeString;
this.customerTypeOfSubset = customerTypeOfSubset;
this.customerTypeMatchesPattern = customerTypeMatchesPattern;
}
public String getCustomerTypeString() {
return customerTypeString;
}
public void setCustomerTypeString(String customerTypeString) {
this.customerTypeString = customerTypeString;
}
public CustomerType getCustomerTypeOfSubset() {
return customerTypeOfSubset;
}
public void setCustomerTypeOfSubset(CustomerType customerTypeOfSubset) {
this.customerTypeOfSubset = customerTypeOfSubset;
}
public CustomerType getCustomerTypeMatchesPattern() {
return customerTypeMatchesPattern;
}
public void setCustomerTypeMatchesPattern(CustomerType customerTypeMatchesPattern) {
this.customerTypeMatchesPattern = customerTypeMatchesPattern;
}
public static class Builder {
private String customerTypeString;
private CustomerType customerTypeOfSubset = CustomerType.NEW;
private CustomerType customerTypeMatchesPattern;
public Builder withCustomerTypeString(String customerTypeString) {
this.customerTypeString = customerTypeString;
return this;
}
public Builder withCustomerTypeOfSubset(CustomerType customerTypeOfSubset) {
this.customerTypeOfSubset = customerTypeOfSubset;
return this;
}
public Builder withCustomerTypeMatchesPattern(CustomerType customerTypeMatchesPattern) {
this.customerTypeMatchesPattern = customerTypeMatchesPattern;
return this;
}
public Customer build() {
return new Customer(customerTypeString, customerTypeOfSubset, customerTypeMatchesPattern);
}
}
}

View File

@ -0,0 +1,5 @@
package org.baeldung.javaxval.enums.demo;
public enum CustomerType {
NEW, OLD, DEFAULT
}

View File

@ -0,0 +1,45 @@
package org.baeldung.javaxval.enums;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.baeldung.javaxval.enums.demo.Customer;
import org.baeldung.javaxval.enums.demo.CustomerType;
import org.baeldung.javaxval.enums.demo.CustomerUnitTest;
import org.junit.BeforeClass;
import org.junit.Test;
public class CustomerTypeSubSetValidatorUnitTest {
private static Validator validator;
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
}
@Test
public void whenEnumAnyOfSubset_thenShouldNotReportConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeOfSubset(CustomerType.NEW)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.isEmpty()).isTrue();
}
@Test
public void whenEnumNotAnyOfSubset_thenShouldGiveOccurrenceOfConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeOfSubset(CustomerType.DEFAULT)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).anyMatch(CustomerUnitTest.havingPropertyPath("customerTypeOfSubset")
.and(CustomerUnitTest.havingMessage("must be any of [NEW, OLD]")));
}
}

View File

@ -0,0 +1,54 @@
package org.baeldung.javaxval.enums;
import static org.assertj.core.api.Assertions.assertThat;
import static org.baeldung.javaxval.enums.demo.CustomerType.DEFAULT;
import static org.baeldung.javaxval.enums.demo.CustomerType.OLD;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.baeldung.javaxval.enums.demo.Customer;
import org.baeldung.javaxval.enums.demo.CustomerUnitTest;
import org.junit.BeforeClass;
import org.junit.Test;
public class EnumNamePatternValidatorUnitTest {
private static Validator validator;
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
}
@Test
public void whenEnumMatchesRegex_thenShouldNotReportConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeMatchesPattern(DEFAULT)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.isEmpty()).isTrue();
}
@Test
public void whenEnumNull_thenShouldNotReportConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeMatchesPattern(null)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.isEmpty()).isTrue();
}
@Test
public void whenEnumDoesNotMatchRegex_thenShouldGiveOccurrenceOfConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeMatchesPattern(OLD)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).anyMatch(CustomerUnitTest.havingPropertyPath("customerTypeMatchesPattern")
.and(CustomerUnitTest.havingMessage("must match \"NEW|DEFAULT\"")));
}
}

View File

@ -0,0 +1,52 @@
package org.baeldung.javaxval.enums;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.baeldung.javaxval.enums.demo.Customer;
import org.baeldung.javaxval.enums.demo.CustomerUnitTest;
import org.junit.BeforeClass;
import org.junit.Test;
public class ValueOfEnumValidatorUnitTest {
private static Validator validator;
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
}
@Test
public void whenStringAnyOfEnum_thenShouldNotReportConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeString("DEFAULT")
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.isEmpty()).isTrue();
}
@Test
public void whenStringNull_thenShouldNotReportConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeString(null)
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.isEmpty()).isTrue();
}
@Test
public void whenStringNotAnyOfEnum_thenShouldGiveOccurrenceOfConstraintViolations() {
Customer customer = new Customer.Builder().withCustomerTypeString("test")
.build();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).anyMatch(CustomerUnitTest.havingPropertyPath("customerTypeString")
.and(CustomerUnitTest.havingMessage("must be any of enum class org.baeldung.javaxval.enums.demo.CustomerType")));
}
}

View File

@ -0,0 +1,66 @@
package org.baeldung.javaxval.enums.demo;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import java.util.function.Predicate;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.baeldung.javaxval.LocaleAwareUnitTest;
import org.junit.BeforeClass;
import org.junit.Test;
public class CustomerUnitTest extends LocaleAwareUnitTest {
private static Validator validator;
@BeforeClass
public static void setupValidatorInstance() {
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
}
@Test
public void whenAllAcceptable_thenShouldNotGiveConstraintViolations() {
Customer customer = new Customer();
customer.setCustomerTypeOfSubset(CustomerType.NEW);
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations).isEmpty();
}
@Test
public void whenAllNull_thenOnlyNotNullShouldGiveConstraintViolations() {
Customer customer = new Customer();
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).anyMatch(havingPropertyPath("customerTypeOfSubset").and(havingMessage("must not be null")));
}
@Test
public void whenAllInvalid_thenViolationsShouldBeReported() {
Customer customer = new Customer();
customer.setCustomerTypeString("invalid");
customer.setCustomerTypeOfSubset(CustomerType.DEFAULT);
customer.setCustomerTypeMatchesPattern(CustomerType.OLD);
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(3);
assertThat(violations).anyMatch(havingPropertyPath("customerTypeString").and(havingMessage("must be any of enum class org.baeldung.javaxval.enums.demo.CustomerType")));
assertThat(violations).anyMatch(havingPropertyPath("customerTypeOfSubset").and(havingMessage("must be any of [NEW, OLD]")));
assertThat(violations).anyMatch(havingPropertyPath("customerTypeMatchesPattern").and(havingMessage("must match \"NEW|DEFAULT\"")));
}
public static Predicate<ConstraintViolation<Customer>> havingMessage(String message) {
return l -> message.equals(l.getMessage());
}
public static Predicate<ConstraintViolation<Customer>> havingPropertyPath(String propertyPath) {
return l -> propertyPath.equals(l.getPropertyPath()
.toString());
}
}