![JAR search and dependency download from the Maven repository](/logo.png)
cz.jirutka.validator.collection.CommonEachValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of validator-collection Show documentation
Show all versions of validator-collection Show documentation
Universal bean validator for collection of simple types.
The newest version!
/*
* The MIT License
*
* Copyright 2013-2016 Jakub Jirutka .
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package cz.jirutka.validator.collection;
import cz.jirutka.validator.collection.constraints.EachConstraint;
import cz.jirutka.validator.collection.internal.ConstraintDescriptorFactory;
import cz.jirutka.validator.collection.internal.MessageInterpolatorContext;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.MessageInterpolator.Context;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.ConstraintDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.TypeVariable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static cz.jirutka.validator.collection.internal.AnnotationUtils.*;
import static cz.jirutka.validator.collection.internal.ConstraintValidatorContextUtils.addConstraintViolationInIterable;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static org.apache.commons.lang3.StringUtils.isEmpty;
/**
* Common validator for collection constraints that validates each element of
* the given collection.
*/
@SuppressWarnings("unchecked")
public class CommonEachValidator implements ConstraintValidator> {
private static final Logger LOG = LoggerFactory.getLogger(CommonEachValidator.class);
private static final ConstraintDescriptorFactory DESCRIPTOR_FACTORY = ConstraintDescriptorFactory.newInstance();
// injected by container, or set default during initialization
private @Inject ValidatorFactory factory;
// after initialization it's read-only
private List descriptors;
// after initialization it's read-only
private Map>> validators;
// modifiable after initialization; must be thread-safe!
private Map validatorInstances;
// after initialization it's read-only
private boolean earlyInterpolation;
public void initialize(Annotation eachAnnotation) {
Class extends Annotation> eachAType = eachAnnotation.annotationType();
LOG.trace("Initializing CommonEachValidator for {}", eachAType);
if (factory == null) {
LOG.debug("No ValidatorFactory injected, building default one");
factory = Validation.buildDefaultValidatorFactory();
}
validatorInstances = new ConcurrentHashMap<>(2);
if (eachAType.isAnnotationPresent(EachConstraint.class)) {
Class constraintClass = eachAType.getAnnotation(EachConstraint.class).validateAs();
Annotation constraint = createConstraintAndCopyAttributes(constraintClass, eachAnnotation);
ConstraintDescriptor descriptor = createConstraintDescriptor(constraint);
descriptors = unmodifiableList(asList(descriptor));
// legacy and deprecated, will be removed in next major version!
} else if (isWrapperAnnotation(eachAType)) {
Annotation[] constraints = unwrapConstraints(eachAnnotation);
Validate.notEmpty(constraints, "%s annotation does not contain any constraint", eachAType);
List list = new ArrayList<>(constraints.length);
for (Annotation constraint : constraints) {
list.add( createConstraintDescriptor(constraint) );
}
descriptors = unmodifiableList(list);
earlyInterpolation = true;
LOG.info("You're using legacy @EachX annotation style, this will be removed soon! " +
"Please update your @EachX annotations.");
} else {
throw new IllegalArgumentException(String.format(
"%s is not annotated with @EachConstraint and doesn't declare 'value' of type Annotation[] either.",
eachAType.getName()));
}
// constraints are always of the same type, so just pick first
ConstraintDescriptor descriptor = descriptors.get(0);
validators = categorizeValidatorsByType(descriptor.getConstraintValidatorClasses());
Validate.notEmpty(validators,
"No validator found for constraint: %s", descriptor.getAnnotation().annotationType());
}
public boolean isValid(Collection> collection, ConstraintValidatorContext context) {
if (collection == null || collection.isEmpty()) {
return true; //nothing to validate here
}
context.disableDefaultConstraintViolation(); //do not add wrapper's message
int index = 0;
for (Iterator> it = collection.iterator(); it.hasNext(); index++) {
Object element = it.next();
ConstraintValidator validator = element != null
? getValidatorInstance(element.getClass())
: getAnyValidatorInstance();
for (ConstraintDescriptor descriptor : descriptors) {
validator.initialize(descriptor.getAnnotation());
if (! validator.isValid(element, context)) {
LOG.debug("Element [{}] = '{}' is invalid according to: {}",
index, element, validator.getClass().getName());
// early interpolation hack is needed only for legacy annotations
// and will go away with them
String message = earlyInterpolation
? createInterpolatedMessage(descriptor, element)
: readAttribute(descriptor.getAnnotation(), "message", String.class);
addConstraintViolationInIterable(context, message, index);
return false;
}
}
}
return true;
}
public void setValidatorFactory(ValidatorFactory factory) {
this.factory = factory;
}
/**
* Whether the given annotation type contains the {@code value} attribute
* of the type that extends {@code Annotation[]}.
*/
protected boolean isWrapperAnnotation(Class extends Annotation> annotationType) {
return hasAttribute(annotationType, "value")
&& Annotation[].class.isAssignableFrom(getAttributeType(annotationType, "value"));
}
protected Annotation[] unwrapConstraints(Annotation wrapper) {
return readAttribute(wrapper, "value", Annotation[].class);
}
protected ConstraintDescriptor createConstraintDescriptor(Annotation constraint) {
return DESCRIPTOR_FACTORY.buildConstraintDescriptor(constraint);
}
protected >
Map> categorizeValidatorsByType(List> validatorClasses) {
Map> validators = new LinkedHashMap<>(10);
for (Class extends T> validator : validatorClasses) {
Class> type = determineTargetType(validator);
if (type.isArray()) continue;
LOG.trace("Found validator {} for type {}", validator.getName(), type.getName());
validators.put(type, validator);
}
return unmodifiableMap(validators);
}
protected Class> determineTargetType(Class extends ConstraintValidator, ?>> validatorClass) {
TypeVariable> typeVar = ConstraintValidator.class.getTypeParameters()[1];
return TypeUtils.getRawType(typeVar, validatorClass);
}
/**
* Returns initialized validator instance for the specified object type.
* Instances are cached.
*
* @param type Type of the object to be validated.
*/
protected ConstraintValidator getValidatorInstance(Class> type) {
ConstraintValidator validator = validatorInstances.get(type);
if (validator == null) {
validator = findAndInitializeValidator(type);
validatorInstances.put(type, validator);
}
return validator;
}
/**
* Returns initialized validator instance for any object type. This is used
* when the object to be validated is null so we can't determine
* it's type. Instances are cached.
*/
protected ConstraintValidator getAnyValidatorInstance() {
if (validatorInstances.isEmpty()) {
Class type = validators.keySet().iterator().next();
return findAndInitializeValidator(type);
} else {
return validatorInstances.values().iterator().next();
}
}
protected ConstraintValidator findAndInitializeValidator(Class> type) {
LOG.trace("Looking for validator for type: {}", type.getName());
for (Class> clazz : validators.keySet()) {
if (clazz.isAssignableFrom(type)) {
Class validatorClass = validators.get(clazz);
LOG.trace("Initializing validator: {}", validatorClass.getName());
return factory.getConstraintValidatorFactory().getInstance(validatorClass);
}
}
throw new IllegalArgumentException("No validator found for type: " + type.getName());
}
/**
* Reads and interpolates an error message for the given constraint and
* value.
*
* @param descriptor Descriptor of the constraint that the value violated.
* @param value The validated value.
* @return An interpolated message.
*/
protected String createInterpolatedMessage(ConstraintDescriptor descriptor, Object value) {
Context context = new MessageInterpolatorContext(descriptor, value);
Annotation constraint = descriptor.getAnnotation();
String template = readAttribute(constraint, "message", String.class);
return factory.getMessageInterpolator().interpolate(template, context);
}
/**
* Instantiates constraint of the specified type and copies values of all
* the common attributes from the given source constraint (of any type)
* to it.
*
* If the source constraint's {@code message} is empty, then it will
* not copy it (so the default {@code message} of the target
* constraint will be preserved).
*
* @param constraintType Type of the constraint to create.
* @param source Any annotation to copy attribute values from.
* @return An instance of the specified constraint.
*/
protected T createConstraintAndCopyAttributes(Class constraintType, Annotation source) {
Map attributes = readAllAttributes(source);
// if message is not set, keep message from original constraint instead
if (isEmpty((String) attributes.get("message"))) {
attributes.remove("message");
}
return createAnnotation(constraintType, attributes);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy