package com.blazebit.validation.constraint.validator;
import com.blazebit.validation.constraint.CheckEither;
import com.blazebit.validation.constraint.PopulationMode;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder;
import javax.validation.ConstraintViolation;
import javax.validation.Path.Node;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class CheckEitherValidator implements
ConstraintValidator<CheckEither, Object> {
private static final ValidatorFactory factory = Validation
.buildDefaultValidatorFactory();
private Class<?>[] value;
private PopulationMode mode;
/*
* Should work with hibernate validator and apache bval, not sure about
* other implementations
*/
private boolean supportsConstraintComposition = true;
@Override
public void initialize(CheckEither constraintAnnotation) {
value = constraintAnnotation.value();
mode = constraintAnnotation.mode();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
if (value.length < 1) {
return false;
}
final Validator validator = factory.getValidator();
Set<ConstraintViolation<Object>> actualViolations = null;
for (final Class<?> groupClass : value) {
final Set<ConstraintViolation<Object>> violations = validator
.validate(object, groupClass);
if (violations.isEmpty()) {
/*
* We found a validation group that succeeds, so stop with
* success
*/
actualViolations = null;
break;
} else {
/* Use population mode rules to preserve the needed violations */
if (actualViolations == null || mode == PopulationMode.LAST) {
actualViolations = new HashSet<ConstraintViolation<Object>>();
}
if (mode == PopulationMode.ALL
|| (mode == PopulationMode.FIRST && actualViolations
.isEmpty()) || mode == PopulationMode.LAST) {
actualViolations = addViolations(violations,
actualViolations);
}
}
}
if (actualViolations != null) {
passViolations(context, actualViolations);
return false;
}
return true;
}
/**
* Add the given source violations to the target set and return the target
* set. This method will skip violations that have a property path depth
* greater than 1.
*
* @param source
* @param target
* @return
*/
private Set<ConstraintViolation<Object>> addViolations(
Set<ConstraintViolation<Object>> source,
Set<ConstraintViolation<Object>> target) {
if (supportsConstraintComposition) {
target.addAll(source);
return target;
}
for (final Iterator<ConstraintViolation<Object>> iter = source
.iterator(); iter.hasNext();) {
final ConstraintViolation<Object> violation = iter.next();
final Iterator<Node> nodeIter = violation.getPropertyPath()
.iterator();
/*
* Only include violations that have no property path or just one
* node
*/
if (!nodeIter.hasNext()
|| (nodeIter.next() != null && !nodeIter.hasNext())) {
target.add(violation);
}
}
return target;
}
/**
* Pass the given violations to the given context. This method will skip
* violations that have a property path depth greater than 1.
*
* @param context
* @param source
*/
private void passViolations(ConstraintValidatorContext context,
Set<ConstraintViolation<Object>> source) {
for (final ConstraintViolation<Object> violation : source) {
final Iterator<Node> nodeIter = violation.getPropertyPath()
.iterator();
final ConstraintViolationBuilder builder = context
.buildConstraintViolationWithTemplate(violation
.getMessageTemplate());
ConstraintValidatorContext nodeContext;
if (nodeIter.hasNext()) {
StringBuilder sb = new StringBuilder(nodeIter.next().getName());
if (supportsConstraintComposition) {
while (nodeIter.hasNext()) {
sb.append('.').append(nodeIter.next());
}
}
builder.addNode(sb.toString()).addConstraintViolation();
} else {
builder.addConstraintViolation();
}
}
}
}