org.apache.bval.jsr.AnnotationProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.bval.jsr.AnnotationProcessor.java

Source

/*
 * 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;
    }

}