| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.bval.jsr; |
| |
| import org.apache.bval.model.Features; |
| import org.apache.bval.model.Meta; |
| import org.apache.bval.model.MetaBean; |
| import org.apache.bval.util.AccessStrategy; |
| import org.apache.bval.util.reflection.Reflection; |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.commons.weaver.privilizer.Privilizing; |
| import org.apache.commons.weaver.privilizer.Privilizing.CallTo; |
| |
| import javax.validation.Constraint; |
| import javax.validation.ConstraintValidator; |
| import javax.validation.Valid; |
| import javax.validation.constraintvalidation.SupportedValidationTarget; |
| import javax.validation.constraintvalidation.ValidationTarget; |
| import javax.validation.groups.ConvertGroup; |
| import javax.validation.groups.Default; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.AnnotatedElement; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Set; |
| |
| /** |
| * Description: implements uniform handling of JSR303 {@link Constraint} |
| * annotations, including composed constraints and the resolution of |
| * {@link ConstraintValidator} implementations. |
| */ |
| @Privilizing(@CallTo(Reflection.class)) |
| public final class AnnotationProcessor { |
| /** {@link ApacheFactoryContext} used */ |
| private final ApacheValidatorFactory factory; |
| |
| /** |
| * Create a new {@link AnnotationProcessor} instance. |
| * |
| * @param factory the validator factory. |
| */ |
| public AnnotationProcessor(ApacheValidatorFactory factory) { |
| this.factory = factory; |
| } |
| |
| /** |
| * Process JSR303 annotations. |
| * |
| * @param prop |
| * potentially null |
| * @param owner |
| * bean type |
| * @param element |
| * whose annotations to read |
| * @param access |
| * strategy for <code>prop</code> |
| * @param appender |
| * handling accumulation |
| * @return whether any processing took place |
| * @throws IllegalAccessException |
| * @throws InvocationTargetException |
| */ |
| public boolean processAnnotations(Meta prop, Class<?> owner, AnnotatedElement element, AccessStrategy access, |
| AppendValidation appender) throws IllegalAccessException, InvocationTargetException { |
| |
| boolean changed = false; |
| for (final Annotation annotation : element.getDeclaredAnnotations()) { |
| final Class<?> type = annotation.annotationType(); |
| if (type.getName().startsWith("java.lang.annotation.")) { |
| continue; |
| } |
| changed = processAnnotation(annotation, prop, owner, access, appender, true) || changed; |
| } |
| return changed; |
| } |
| |
| /** |
| * Process a single annotation. |
| * |
| * @param <A> |
| * annotation type |
| * @param annotation |
| * to process |
| * @param prop |
| * potentially null |
| * @param owner |
| * bean type |
| * @param access |
| * strategy for <code>prop</code> |
| * @param appender |
| * handling accumulation |
| * @return whether any processing took place |
| * @throws IllegalAccessException |
| * @throws InvocationTargetException |
| */ |
| public <A extends Annotation> boolean processAnnotation(A annotation, Meta prop, Class<?> owner, |
| AccessStrategy access, AppendValidation appender, boolean reflection) throws IllegalAccessException, |
| InvocationTargetException { |
| if (annotation instanceof Valid) { |
| return addAccessStrategy(prop, access); |
| } |
| |
| if (ConvertGroup.class.isInstance(annotation) || ConvertGroup.List.class.isInstance(annotation)) { |
| if (!reflection) { |
| Collection<Annotation> annotations = prop.getFeature(JsrFeatures.Property.ANNOTATIONS_TO_PROCESS); |
| if (annotations == null) { |
| annotations = new ArrayList<Annotation>(); |
| prop.putFeature(JsrFeatures.Property.ANNOTATIONS_TO_PROCESS, annotations); |
| } |
| annotations.add(annotation); |
| } |
| return true; |
| } |
| |
| /** |
| * An annotation is considered a constraint definition if its retention |
| * policy contains RUNTIME and if the annotation itself is annotated |
| * with javax.validation.Constraint. |
| */ |
| final Constraint vcAnno = annotation.annotationType().getAnnotation(Constraint.class); |
| if (vcAnno != null) { |
| Class<? extends ConstraintValidator<A, ?>>[] 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. |
| */ |
| final Object result = |
| Reflection.getAnnotationValue(annotation, ConstraintAnnotationAttributes.VALUE.getAttributeName()); |
| if (result instanceof Annotation[]) { |
| boolean changed = false; |
| for (final Annotation each : (Annotation[]) result) { |
| if (each.annotationType().getName().startsWith("java.lang.annotation")) { |
| continue; |
| } |
| |
| changed |= processAnnotation(each, prop, owner, access, appender, reflection); |
| } |
| return changed; |
| } |
| return false; |
| } |
| |
| /** |
| * Add the specified {@link AccessStrategy} to <code>prop</code>; noop if |
| * <code>prop == null</code>. |
| * |
| * @param prop |
| * @param access |
| * @return whether anything took place. |
| */ |
| public boolean addAccessStrategy(Meta prop, AccessStrategy access) { |
| if (prop == null) { |
| return false; |
| } |
| AccessStrategy[] strategies = prop.getFeature(Features.Property.REF_CASCADE); |
| if (ArrayUtils.contains(strategies, access)) { |
| return false; |
| } |
| if (strategies == null) { |
| strategies = new AccessStrategy[] { access }; |
| } else { |
| strategies = ArrayUtils.add(strategies, access); |
| } |
| prop.putFeature(Features.Property.REF_CASCADE, strategies); |
| 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 <A extends Annotation> Class<? extends ConstraintValidator<A, ?>>[] findConstraintValidatorClasses( |
| A annotation, Constraint vcAnno) { |
| if (vcAnno == null) { |
| vcAnno = annotation.annotationType().getAnnotation(Constraint.class); |
| } |
| final Class<A> annotationType = (Class<A>) annotation.annotationType(); |
| Class<? extends ConstraintValidator<A, ?>>[] validatorClasses = |
| factory.getConstraintsCache().getConstraintValidators(annotationType); |
| if (validatorClasses == null) { |
| validatorClasses = (Class<? extends ConstraintValidator<A, ?>>[]) vcAnno.validatedBy(); |
| if (validatorClasses.length == 0) { |
| validatorClasses = factory.getDefaultConstraints().getValidatorClasses(annotationType); |
| } |
| } |
| return validatorClasses; |
| } |
| |
| /** |
| * Apply a constraint to the specified <code>appender</code>. |
| * |
| * @param annotation |
| * constraint annotation |
| * @param rawConstraintClasses |
| * known {@link ConstraintValidator} implementation classes for |
| * <code>annotation</code> |
| * @param prop |
| * meta-property |
| * @param owner |
| * type |
| * @param access |
| * strategy |
| * @param appender |
| * @return success flag |
| * @throws IllegalAccessException |
| * @throws InvocationTargetException |
| */ |
| private <A extends Annotation> boolean applyConstraint( |
| A annotation, |
| Class<? extends ConstraintValidator<A, ?>>[] rawConstraintClasses, Meta prop, Class<?> owner, |
| AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException { |
| |
| final Class<? extends ConstraintValidator<A, ?>>[] constraintClasses = select(rawConstraintClasses, access); |
| if (constraintClasses != null && constraintClasses.length == 0 && rawConstraintClasses.length > 0) { |
| return false; |
| } |
| |
| final AnnotationConstraintBuilder<A> builder = |
| new AnnotationConstraintBuilder<A>( |
| constraintClasses, |
| annotation, owner, access, null); |
| |
| // JSR-303 3.4.4: Add implicit groups |
| if (prop != null && prop.getParentMetaBean() != null) { |
| final 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<Class<?>> 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 static <A extends Annotation> Class<? extends ConstraintValidator<A, ?>>[] select( |
| final Class<? extends ConstraintValidator<A, ?>>[] rawConstraintClasses, final AccessStrategy access) { |
| final boolean isReturn = ReturnAccess.class.isInstance(access); |
| final boolean isParam = ParametersAccess.class.isInstance(access); |
| if (rawConstraintClasses != null && (isReturn || isParam)) { |
| final Collection<Class<? extends ConstraintValidator<A, ?>>> selected = |
| new ArrayList<Class<? extends ConstraintValidator<A, ?>>>(); |
| for (final Class<? extends ConstraintValidator<A, ?>> constraint : rawConstraintClasses) { |
| final SupportedValidationTarget target = constraint.getAnnotation(SupportedValidationTarget.class); |
| if (target == null && isReturn) { |
| selected.add(constraint); |
| } else if (target != null) { |
| for (final ValidationTarget validationTarget : target.value()) { |
| if (isReturn && ValidationTarget.ANNOTATED_ELEMENT == validationTarget) { |
| selected.add(constraint); |
| } else if (isParam && ValidationTarget.PARAMETERS == validationTarget) { |
| selected.add(constraint); |
| } |
| } |
| } |
| } |
| @SuppressWarnings("unchecked") |
| final Class<? extends ConstraintValidator<A, ?>>[] result = selected.toArray(new Class[selected.size()]); |
| return result; |
| } |
| return rawConstraintClasses; |
| } |
| |
| } |