com.lotaris.jee.validation.AbstractConstraintValidator Maven / Gradle / Ivy
Show all versions of jee-validation Show documentation
package com.lotaris.jee.validation;
import java.lang.annotation.Annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* Basic bean validation constraint implementation. When using this class, you will implement the
* validate method instead of the isValid method from {@link ConstraintValidator}.
* This allows the abstract validator to hide bean validation implementation details from you,
* making validations easier to write and test.
*
* What to implement
*
* You must implement the validate method. A {@link IConstraintValidationContext} will
* be passed to your method. This object will allow you to add errors if you determine that the
* supplied value/object is invalid.
*
* Minimal implementation
*
* This validator implementation will use the default error message defined by your constraint
* annotation.
*
*
* public class MyAnnotationValidator extends AbstractConstraintValidator<MyAnnotation, String> {
*
* @Override
* public void validate(String value, IConstraintValidationContext context) {
* if (isInvalid(value)) {
* context.addDefaultError();
* }
* }
* }
*
*
* Custom errors
*
* Sometimes the static error message defined by your constraint annotation isn't enough. You
* might need to build the message dynamically based on the validated value. In this case, use the
* addError methods to override the default error message.
*
*
* public class MyAnnotationValidator extends AbstractConstraintValidator<MyAnnotation, Object> {
*
* @Override
* public void validate(Object value, IConstraintValidationContext context) {
*
* if (isInvalid(value)) {
*
* // add the default error message
* context.addDefaultError();
*
* // add another error message
* context.addErrorAtCurrentLocation("Object is invalid.");
*
* // additional arguments are interpolated
* context.addErrorAtCurrentLocation("%s is invalid", value);
*
* // if you are validating a complex object, you can add errors to a specific property
* context.addError("name", "Name must be at most 25 characters long, got %d.", value.getName().length());
* }
* }
* }
*
*
* What to test
*
* When testing, only test the validate method and not the isValid method. This
* allows you to mock the validation context and ignore bean validation implementation details.
*
*
* public class CheckStringLengthValidatorUnitTests {
*
* @Mock
* private IConstraintValidationContext context;
* private CheckStringLengthValidator validator;
*
* @Before
* public void setUp() {
* MockitoAnnotations.initMocks(this);
* validator = new CheckStringLengthValidator();
* }
*
* @Test
* public void checkStringLengthValidatorShouldFailIfStringIsTooLong() {
* validator.validate("foooooooooooooo", context);
* verify(context).addDefaultError();
* verifyNoMoreInteractions(context);
* }
* }
*
*
* @author Simon Oulevay ([email protected])
* @param the constraint annotation
* @param the type of value to validate (e.g. String)
*/
public abstract class AbstractConstraintValidator implements ConstraintValidator {
@Override
public void initialize(A constraintAnnotation) {
// do nothing by default
}
/**
* Validates the specified object.
*
* @param object the object to validate
* @param context the context used to add and keep track of errors during validation
*/
public abstract void validate(T value, IConstraintValidationContext context);
@Override
public final boolean isValid(T value, ConstraintValidatorContext context) {
final IConstraintValidationContext wrapperContext = new ConstraintValidationContext(context);
validate(value, wrapperContext);
return !wrapperContext.hasErrors();
}
/**
* Wrapper around {@link ConstraintValidatorContext} to provide an easier-to-use validation API.
*/
private static class ConstraintValidationContext implements IConstraintValidationContext {
private boolean hasErrors;
private final ConstraintValidatorContext context;
public ConstraintValidationContext(ConstraintValidatorContext context) {
this.hasErrors = false;
this.context = context;
}
private ConstraintValidatorContext addErrors() {
if (!hasErrors) {
context.disableDefaultConstraintViolation();
hasErrors = true;
}
return context;
}
@Override
public boolean hasErrors() {
return hasErrors;
}
@Override
public IConstraintValidationContext addDefaultError() {
addErrors().buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addConstraintViolation();
return this;
}
@Override
public IConstraintValidationContext addError(String location, String message, Object... messageArgs) {
location = validateLocation(location);
addErrors().buildConstraintViolationWithTemplate(String.format(message, messageArgs)).addPropertyNode(location).addConstraintViolation();
return this;
}
@Override
public IConstraintValidationContext addArrayError(String location, int index, String message, Object... messageArgs) {
location = validateLocation(location);
addErrors().buildConstraintViolationWithTemplate(String.format(message, messageArgs)).addPropertyNode(location).addPropertyNode(String.valueOf(index)).addConstraintViolation();
return this;
}
@Override
public IConstraintValidationContext addErrorAtCurrentLocation(String message, Object... messageArgs) {
addErrors().buildConstraintViolationWithTemplate(String.format(message, messageArgs)).addConstraintViolation();
return this;
}
/**
* Validate and sanitize the error location.
* @param location The location
* @return The sanitized location.
* @throws IllegalArgumentException If the location contains dots or slash
*/
private String validateLocation(String location) throws IllegalArgumentException {
if (location.contains(".")) {
throw new IllegalArgumentException("Constraint violation error locations cannot contain dots.");
}
location = location.replaceFirst("^\\/", "");
if (location.contains("/")) {
throw new IllegalArgumentException("Constraint violation error locations cannot contain a slash except as the first character.");
}
return location;
}
}
}