All Downloads are FREE. Search and download functionalities are using the official Maven repository.

br.com.anteros.bean.validation.AnnotationProcessor Maven / Gradle / Ivy

There is a newer version: 1.0.18
Show newest version
/*******************************************************************************
 * 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 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>[] 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>[] findConstraintValidatorClasses(
			A annotation, Constraint vcAnno) {
		if (vcAnno == null) {
			vcAnno = annotation.annotationType().getAnnotation(Constraint.class);
		}
		Class>[] validatorClasses;
		Class annotationType = (Class) annotation.annotationType();
		validatorClasses = factoryContext.getFactory().getConstraintsCache().getConstraintValidators(annotationType);
		if (validatorClasses == null) {
			validatorClasses = (Class>[]) 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>[] 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>[] 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>[] constraintValidatorClasses) {
		Map>> validatorsTypes = new HashMap>>();
		for (Class> 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