br.com.anteros.bean.validation.AnnotationProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Anteros-Bean-Validation Show documentation
Show all versions of Anteros-Bean-Validation Show documentation
Anteros Bean Validation for Java.
/*******************************************************************************
* Copyright 2012 Anteros Tecnologia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package br.com.anteros.bean.validation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import br.com.anteros.bean.validation.constraints.Length;
import br.com.anteros.bean.validation.model.Features;
import br.com.anteros.bean.validation.model.MetaBean;
import br.com.anteros.bean.validation.model.MetaProperty;
import br.com.anteros.bean.validation.util.AccessStrategy;
import br.com.anteros.bean.validation.util.ConstraintDefinitionValidator;
import br.com.anteros.bean.validation.util.SecureActions;
import br.com.anteros.core.utils.ArrayUtils;
import br.com.anteros.core.utils.ClassUtils;
import br.com.anteros.core.utils.TypeUtils;
import br.com.anteros.validation.api.Constraint;
import br.com.anteros.validation.api.ConstraintValidator;
import br.com.anteros.validation.api.UnexpectedTypeException;
import br.com.anteros.validation.api.Valid;
import br.com.anteros.validation.api.ValidationException;
import br.com.anteros.validation.api.groups.Default;
/**
* Description: implements uniform handling of JSR303 {@link Constraint}
* annotations, including composed constraints and the resolution of
* {@link ConstraintValidator} implementations.
*/
public final class AnnotationProcessor {
/** {@link AnterosFactoryContext} used */
private final AnterosFactoryContext factoryContext;
/**
* Create a new {@link AnnotationProcessor} instance.
*
* @param factoryContext
*/
public AnnotationProcessor(AnterosFactoryContext factoryContext) {
this.factoryContext = factoryContext;
}
/**
* Process JSR303 annotations.
*
* @param prop potentially null
* @param owner bean type
* @param element whose annotations to read
* @param access strategy for prop
* @param appender handling accumulation
* @return whether any processing took place
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public boolean processAnnotations(MetaProperty prop, Class> owner, AnnotatedElement element,
AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
boolean changed = false;
for (Annotation annotation : element.getDeclaredAnnotations()) {
changed |= processAnnotation(annotation, prop, owner, access, appender);
}
return changed;
}
/**
* Convenience method to process a single class-level annotation.
*
* @param annotation type
* @param annotation to process
* @param owner bean type
* @param appender handling accumulation
* @return whether any processing took place
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public final boolean processAnnotation(A annotation, Class> owner,
AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
return processAnnotation(annotation, null, owner, null, appender);
}
/**
* Process a single annotation.
*
* @param annotation type
* @param annotation to process
* @param prop potentially null
* @param owner bean type
* @param access strategy for prop
* @param appender handling accumulation
* @return whether any processing took place
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public boolean processAnnotation(A annotation, MetaProperty prop, Class> owner,
AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
if (annotation instanceof Valid) {
return addAccessStrategy(prop, access);
}
//
// Class extends Annotation> type = annotation.annotationType();
//
// A tempAnnotation = annotation;
// if (type.getSimpleName().equalsIgnoreCase("Column")) {
// for (Method method : type.getDeclaredMethods()) {
// if (method.getName().equals("length")) {
// Object value = method.invoke(annotation, (Object[]) null);
// int length = Integer.parseInt(value + "");
// if (length > 0) {
// Map annotationParameters = new HashMap<>();
// annotationParameters.put("max", length);
// annotationParameters.put("min", 0);
// Length ann = (Length) sun.reflect.annotation.AnnotationParser.annotationForMap(Length.class,
// annotationParameters);
// tempAnnotation = (A) ann;
// }
// }
// }
// }
/**
* An annotation is considered a constraint definition if its retention policy
* contains RUNTIME and if the annotation itself is annotated with
* javax.validation.Constraint.
*/
Constraint vcAnno = annotation.annotationType().getAnnotation(Constraint.class);
if (vcAnno != null) {
ConstraintDefinitionValidator.validateConstraintDefinition(annotation);
Class extends ConstraintValidator>[] validatorClasses;
validatorClasses = findConstraintValidatorClasses(annotation, vcAnno);
return applyConstraint(annotation, validatorClasses, prop, owner, access, appender);
}
/**
* Multi-valued constraints: To support this requirement, the bean validation
* provider treats regular annotations (annotations not annotated
* by @Constraint) whose value element has a return type of an array of
* constraint annotations in a special way.
*/
Object result = SecureActions.getAnnotationValue(annotation,
ConstraintAnnotationAttributes.VALUE.getAttributeName());
if (result instanceof Annotation[]) {
boolean changed = false;
for (Annotation each : (Annotation[]) result) {
changed |= processAnnotation(each, prop, owner, access, appender);
}
return changed;
}
return false;
}
/**
* Add the specified {@link AccessStrategy} to prop
; noop if
* prop == null
.
*
* @param prop
* @param access
* @return whether anything took place.
*/
public boolean addAccessStrategy(MetaProperty prop, AccessStrategy access) {
if (prop == null) {
return false;
}
AccessStrategy[] strategies = prop.getFeature(Features.Property.REF_CASCADE);
if (strategies == null) {
strategies = new AccessStrategy[] { access };
prop.putFeature(Features.Property.REF_CASCADE, strategies);
} else if (!ArrayUtils.contains(strategies, access)) {
AccessStrategy[] newStrategies = new AccessStrategy[strategies.length + 1];
System.arraycopy(strategies, 0, newStrategies, 0, strategies.length);
newStrategies[strategies.length] = access;
prop.putFeature(Features.Property.REF_CASCADE, newStrategies);
}
return true;
}
/**
* Find available {@link ConstraintValidation} classes for a given constraint
* annotation.
*
* @param annotation
* @param vcAnno
* @return {@link ConstraintValidation} implementation class array
*/
@SuppressWarnings("unchecked")
private Class extends ConstraintValidator>[] findConstraintValidatorClasses(
A annotation, Constraint vcAnno) {
if (vcAnno == null) {
vcAnno = annotation.annotationType().getAnnotation(Constraint.class);
}
Class extends ConstraintValidator>[] validatorClasses;
Class annotationType = (Class) annotation.annotationType();
validatorClasses = factoryContext.getFactory().getConstraintsCache().getConstraintValidators(annotationType);
if (validatorClasses == null) {
validatorClasses = (Class extends ConstraintValidator>[]) vcAnno.validatedBy();
if (validatorClasses.length == 0) {
validatorClasses = factoryContext.getFactory().getDefaultConstraints()
.getValidatorClasses(annotationType);
}
}
return validatorClasses;
}
/**
* Apply a constraint to the specified appender
.
*
* @param annotation constraint annotation
* @param constraintClasses known {@link ConstraintValidator} implementation
* classes for annotation
* @param prop meta-property
* @param owner type
* @param access strategy
* @param appender
* @return success flag
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private boolean applyConstraint(A annotation,
Class extends ConstraintValidator>[] constraintClasses, MetaProperty prop, Class> owner,
AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
final ConstraintValidator validator = getConstraintValidator(annotation, constraintClasses, owner,
access);
final AnnotationConstraintBuilder builder = new AnnotationConstraintBuilder(constraintClasses, validator,
annotation, owner, access);
// JSR-303 3.4.4: Add implicit groups
if (prop != null && prop.getParentMetaBean() != null) {
MetaBean parentMetaBean = prop.getParentMetaBean();
// If:
// - the owner is an interface
// - the class of the metabean being build is different than the
// owner
// - and only the Default group is defined
// Then: add the owner interface as implicit groups
if (builder.getConstraintValidation().getOwner().isInterface()
&& parentMetaBean.getBeanClass() != builder.getConstraintValidation().getOwner()
&& builder.getConstraintValidation().getGroups().size() == 1
&& builder.getConstraintValidation().getGroups().contains(Default.class)) {
Set> groups = builder.getConstraintValidation().getGroups();
groups.add(builder.getConstraintValidation().getOwner());
builder.getConstraintValidation().setGroups(groups);
}
}
// If already building a constraint composition tree, ensure that:
// - the parent groups are inherited
// - the parent payload is inherited
if (appender instanceof AppendValidationToBuilder) {
AppendValidationToBuilder avb = (AppendValidationToBuilder) appender;
builder.getConstraintValidation().setGroups(avb.getInheritedGroups());
builder.getConstraintValidation().setPayload(avb.getInheritedPayload());
}
// process composed constraints:
// here are not other superclasses possible, because annotations do not
// inherit!
processAnnotations(prop, owner, annotation.annotationType(), access, new AppendValidationToBuilder(builder));
// Even if the validator is null, it must be added to mimic the RI impl
appender.append(builder.getConstraintValidation());
return true;
}
private ConstraintValidator getConstraintValidator(A annotation,
Class extends ConstraintValidator>[] constraintClasses, Class> owner, AccessStrategy access) {
if (constraintClasses != null && constraintClasses.length > 0) {
Type type = determineTargetedType(owner, access);
/**
* spec says in chapter 3.5.3.: The ConstraintValidator chosen to validate a
* declared type T is the one where the type supported by the
* ConstraintValidator is a supertype of T and where there is no other
* ConstraintValidator whose supported type is a supertype of T and not a
* supertype of the chosen ConstraintValidator supported type.
*/
Map>> validatorTypes = getValidatorsTypes(
constraintClasses);
final List assignableTypes = new ArrayList(constraintClasses.length);
fillAssignableTypes(type, validatorTypes.keySet(), assignableTypes);
reduceAssignableTypes(assignableTypes);
checkOneType(assignableTypes, type, owner, annotation, access);
@SuppressWarnings("unchecked")
final ConstraintValidator validator = (ConstraintValidator) factoryContext
.getConstraintValidatorFactory().getInstance(validatorTypes.get(assignableTypes.get(0)));
if (validator == null) {
throw new ValidationException(
"Factory returned null validator for: " + validatorTypes.get(assignableTypes.get(0)));
}
return validator;
// NOTE: validator initialization deferred until append phase
}
return null;
}
private static void checkOneType(List types, Type targetType, Class> owner, Annotation anno,
AccessStrategy access) {
if (types.isEmpty()) {
StringBuilder buf = new StringBuilder().append("No validator could be found for type ")
.append(stringForType(targetType)).append(". See: @").append(anno.annotationType().getSimpleName())
.append(" at ").append(stringForLocation(owner, access));
throw new UnexpectedTypeException(buf.toString());
} else if (types.size() > 1) {
StringBuilder buf = new StringBuilder();
buf.append("Ambiguous validators for type ");
buf.append(stringForType(targetType));
buf.append(". See: @").append(anno.annotationType().getSimpleName()).append(" at ")
.append(stringForLocation(owner, access));
buf.append(". Validators are: ");
boolean comma = false;
for (Type each : types) {
if (comma)
buf.append(", ");
comma = true;
buf.append(each);
}
throw new UnexpectedTypeException(buf.toString());
}
}
/** implements spec chapter 3.5.3. ConstraintValidator resolution algorithm. */
private static Type determineTargetedType(Class> owner, AccessStrategy access) {
// if the constraint declaration is hosted on a class or an interface,
// the targeted type is the class or the interface.
if (access == null)
return owner;
Type type = access.getJavaType();
if (type == null)
return Object.class;
if (type instanceof Class>)
type = ClassUtils.primitiveToWrapper((Class>) type);
return type;
}
private static String stringForType(Type clazz) {
if (clazz instanceof Class>) {
if (((Class>) clazz).isArray()) {
return ((Class>) clazz).getComponentType().getName() + "[]";
} else {
return ((Class>) clazz).getName();
}
} else {
return clazz.toString();
}
}
private static String stringForLocation(Class> owner, AccessStrategy access) {
if (access != null) {
return access.toString();
} else {
return owner.getName();
}
}
private static void fillAssignableTypes(Type type, Set validatorsTypes, List suitableTypes) {
for (Type validatorType : validatorsTypes) {
if (TypeUtils.isAssignable(type, validatorType) && !suitableTypes.contains(validatorType)) {
suitableTypes.add(validatorType);
}
}
}
/**
* Tries to reduce all assignable classes down to a single class.
*
* @param assignableTypes The set of all classes which are assignable to the
* class of the value to be validated and which are
* handled by at least one of the validators for the
* specified constraint.
*/
private static void reduceAssignableTypes(List assignableTypes) {
if (assignableTypes.size() <= 1) {
return; // no need to reduce
}
boolean removed;
do {
removed = false;
final Type type = assignableTypes.get(0);
for (int i = 1; i < assignableTypes.size(); i++) {
Type nextType = assignableTypes.get(i);
if (TypeUtils.isAssignable(nextType, type)) {
assignableTypes.remove(0);
i--;
removed = true;
} else if (TypeUtils.isAssignable(type, nextType)) {
assignableTypes.remove(i--);
removed = true;
}
}
} while (removed && assignableTypes.size() > 1);
}
/**
* Given a set of {@link ConstraintValidator} implementation classes, map those
* to their target types.
*
* @param constraintValidatorClasses
* @return {@link Map} of {@link Type} : {@link ConstraintValidator} subtype
*/
private static Map>> getValidatorsTypes(
Class extends ConstraintValidator>[] constraintValidatorClasses) {
Map>> validatorsTypes = new HashMap>>();
for (Class extends ConstraintValidator> validatorType : constraintValidatorClasses) {
Map, Type> typeArguments = TypeUtils.getTypeArguments(validatorType,
ConstraintValidator.class);
Type validatedType = typeArguments.get(ConstraintValidator.class.getTypeParameters()[1]);
if (validatedType == null) {
throw new ValidationException(String.format("Could not detect validated type for %s", validatorType));
}
if (validatedType instanceof GenericArrayType) {
Type componentType = TypeUtils.getArrayComponentType(validatedType);
if (componentType instanceof Class>) {
validatedType = Array.newInstance((Class>) componentType, 0).getClass();
}
}
validatorsTypes.put(validatedType, validatorType);
}
return validatorsTypes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy