Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ydemartino/f761bed832f61a827ef505939b22011c to your computer and use it in GitHub Desktop.
Save ydemartino/f761bed832f61a827ef505939b22011c to your computer and use it in GitHub Desktop.
https://resourcepool.io/2014/08/21/java-quickies-hibernate-validator-reference-implementation-of-jsr-303/
package io.resourcepool.model;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.NotBlank;
public class Address {
private String line1;
private String line2;
private String line3;
@Pattern(regexp = "[0-9]{5}", message = "Invalid zip code!")
private String zipCode;
@NotBlank
private String city;
@NotBlank
private String country;
// Getters & Setters
}
List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setZipCode("00000");
address.add(addresse);
client.setAddresses(addresses);
service.validateClient(client);
package io.resourcepool.validator.constraint;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = AtLeastOneConstraint.AtLeastOneValidator.class)
public @interface AtLeastOneConstraint {
String message() default "{io.resourcepool.validator.AtLeastOneConstraint.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fieldNames();
public static class AtLeastOneValidator implements ConstraintValidator<AtLeastOneConstraint, Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(AtLeastOneValidator.class);
private String[] fieldNames;
public void initialize(AtLeastOneConstraint constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
if (object == null) {
return true;
}
try {
for (String field : fieldNames) {
Object property = tryGetField(object, field, "get", "is");
if (property != null) {
return true;
}
}
} catch (Exception e) {
LOGGER.error("Exception during AtLeastOne validation", e);
}
return false;
}
private Object tryGetField(Object object, String field, String... prefixes)
throws IllegalAccessException, InvocationTargetException {
for (String prefix: prefixes) {
try {
return object.getClass().getMethod(prefix + field.substring(0, 1).toUpperCase() + field.substring(1))
.invoke(object);
} catch (NoSuchMethodException ignore) {
// Nothing to do
}
}
return null;
}
}
}
@AtLeastOneConstraint(fieldNames = { "line1", "line2", "line3" })
public class Address
public static class AtLeastOneValidator implements ConstraintValidator<AtLeastOneConstraint, Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(AtLeastOneValidator.class);
private String[] fieldNames;
public void initialize(AtLeastOneConstraint constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
if (subValidation(object)) {
return true;
}
// We need to link the error to the correct fields
for (String field : fieldNames) {
ValidatorUtil.linkErrorToProperty(field, constraintContext);
}
return false;
}
private boolean subValidation(Object object) {
if (object == null) {
return true;
}
try {
for (String field : fieldNames) {
Object property = tryGetField(object, field, "get", "is");
if (property != null) {
return true;
}
}
} catch (Exception e) {
LOGGER.error("Exception during AtLeastOneValidator validation", e);
}
return false;
}
private Object tryGetField(Object object, String field, String... prefixes)
throws IllegalAccessException, InvocationTargetException {
for (String prefix: prefixes) {
try {
return object.getClass().getMethod(prefix + field.substring(0, 1).toUpperCase() + field.substring(1))
.invoke(object);
} catch (NoSuchMethodException ignore) {
// Nothing to do!
}
}
return null;
}
}
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean id="ValidatorUtil" class="io.resourcepool.validator.ValidatorUtil">
<property name="validator" ref="validator"/>
</bean>
package io.resourcepool.model;
import java.util.Date;
import javax.validation.constraints.Past;
import org.hibernate.validator.constraints.*;
public class Client {
private Long id;
@NotBlank
private String firstName;
@NotBlank
private String lastName;
@Email
private String email;
@Past
private Date birthDate;
// Getters & Setters
}
package io.resourcepool.model;
import java.util.Date;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.*;
import org.hibernate.validator.constraints.*;
import io.resourcepool.validator.ClientBase;
public class Client {
private Long id;
@NotBlank(groups = ClientBase.class)
private String firstName;
@NotBlank(groups = ClientBase.class)
private String lastName;
@Email(groups = ClientBase.class)
@NotNull(groups = ClientBase.class)
private String email;
@Past(groups = ClientBase.class)
@NotNull(groups = ClientBase.class, message = "You must set your date of birth")
private Date birthDate;
@Size(groups = ClientBase.class, min = 2, max = 2, message = "You need at least {min} addresses")
@NotNull(groups = ClientBase.class)
@Valid
private List<Address> addresses;
}
package io.resourcepool.validator;
public interface ClientBase {
}
Set<ConstraintViolation<Client>> violations = validator.validate(client, ClientBase.class);
@Email
@NotNull
private String email;
@Past
@NotNull
private Date birthDate;
public class ClientService {
private Validator validator;
public ClientService() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
private void validateClient(Client client) {
Set<ConstraintViolation<Client>> violations = validator.validate(client);
// Process errors if any
}
}
Client client = new Client();
client.setFirstName("Yoann");
client.setEmail("Yoann");
service.validateClient(client);
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd"
xmlns="http://jboss.org/xml/ns/javax/validation/mapping">
<constraint-definition annotation="io.resourcepool.validator.constraints.UniqueEmailConstraint">
<validated-by include-existing-validators="false">
<value>io.resourcepool.validator.UniqueEmailValidator</value>
</validated-by>
</constraint-definition>
</constraint-mappings>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.2.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
@Constraint(validatedBy = { })
public @interface NotNull
@Past
@NotNull(message = "You must set your date of birth")
private Date birthDate;
@Size(min = 2, max = 2, message = "You need at least {min} addresses")
@NotNull
@Valid
private List<Address> addresses;
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { UniqueEmailConstraint.UniqueEmailValidator.class })
public @interface UniqueEmailConstraint {
String message() default "{io.resourcepool.validator.constraint.UniqueEmailConstraint.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public static class UniqueEmailValidator implements ConstraintValidator<UniqueEmailConstraint, Client> {
@Autowired
private Client clientDao;
@Override
public void initialize(UniqueEmailConstraint constraintAnnotation) {
// Nothing to do
}
@Override
public boolean isValid(Client client, ConstraintValidatorContext context) {
return ValidatorUtil.linkErrorToProperty("email", context, subValidation(client));
}
private boolean subValidation(Client client) {
if (client == null || client.getEmail() == null) {
return true;
}
Client res = clientDao.getByEmail(client.getEmail());
// Valid if the email does not exist in DB or if it is already used by the current client
return res == null || client.getId() != null && client.getId().equals(res.getId());
}
}
}
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">
<constraint-mapping>/constraints-client.xml</constraint-mapping>
</validation-config>
io.resourcepool.validator.AtLeastOneConstraint.message=You need at least one of these fields.
package io.resourcepool.validator;
public class ValidatorUtil {
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context) {
return linkErrorToProperty(property, context, false);
}
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context, boolean isValid) {
if (isValid) {
// Nothing to do if it is valid !
return true;
}
// If it is invalid, we need to link it to the correct field
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(property).addConstraintViolation();
return false;
}
}
public class ValidatorUtil {
private static Validator validator;
static {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
public static <T> Map<String, String> validate(T object, Class<?>... groups) {
Class<?>[] args = Arrays.copyOf(groups, groups.length + 1);
args[groups.length] = Default.class;
return extractViolations(validator.validate(object, args));
}
private static <T> Map<String, String> extractViolations(Set<ConstraintViolation<T>> violations) {
Map<String, String> errors = new HashMap<String, String>();
for (ConstraintViolation<T> v: violations) {
errors.put(v.getPropertyPath().toString(), v.getMessage());
}
return errors;
}
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context) {
return linkErrorToProperty(property, context, false);
}
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context, boolean isValid) {
if (isValid) {
// Nothing to do if it is valid !
return true;
}
// If it is invalid, we need to link it to the correct field
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(property).addConstraintViolation();
return false;
}
}
public class ValidatorUtil {
private Validator validator;
private static ThreadLocal<Map<String, Object>> validationContext = new ThreadLocal<>();
public void setValidator(Validator validator) {
this.validator = validator;
}
public <T> Map<String, String> validate(T object, Class<?>... groups) {
Class<?>[] args = Arrays.copyOf(groups, groups.length + 1);
args[groups.length] = Default.class;
return extractViolations(validator.validate(object, args));
}
public static <T> T getValidationVariable(String key) {
// We are using containsKey not to throw an exception if the value is null
if (validationContext.get() != null && validationContext.get().containsKey(key)) {
return (T)validationContext.get().get(key);
}
throw new CannotValidateException(key + " doest not exist or the context is null!");
}
public static void addValidationVariable(String key, Object value) {
if (validationContext.get() == null) {
validationContext.set(new HashMap<String, Object>());
}
validationContext.get().put(key, value);
}
public static void clearValidationVariables() {
validationContext.set(null);
}
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context) {
return linkErrorToProperty(property, context, false);
}
public static boolean linkErrorToProperty(String property, ConstraintValidatorContext context, boolean isValid) {
if (isValid) {
return true;
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(property).addConstraintViolation();
return false;
}
private <T> Map<String, String> extractViolations(Set<ConstraintViolation<T>> violations) {
Map<String, String> errors = new HashMap<String, String>();
for (ConstraintViolation<T> v: violations) {
errors.put(v.getPropertyPath().toString(), v.getMessage());
}
return errors;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment