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

org.apache.bval.cdi.BValInterceptor Maven / Gradle / Ivy

There is a newer version: 10.0.0-M3
Show newest version
/*
 * 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.cdi;

import org.apache.bval.jsr.util.ClassHelper;
import org.apache.bval.jsr.util.Proxies;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Priority;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.CDI;
import javax.inject.Inject;
import javax.interceptor.AroundConstruct;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InterceptorBinding;
import javax.interceptor.InvocationContext;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.executable.ExecutableType;
import javax.validation.executable.ExecutableValidator;
import javax.validation.executable.ValidateOnExecution;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.MethodDescriptor;

import static java.util.Arrays.asList;

/**
 * Interceptor class for the {@link BValBinding} {@link InterceptorBinding}.
 */
@Interceptor
@BValBinding
@Priority(4800)
// TODO: maybe add it through ASM to be compliant with CDI 1.0 containers using simply this class as a template to
// generate another one for CDI 1.1 impl
public class BValInterceptor implements Serializable {
    private transient volatile Map methodConfiguration = new ConcurrentHashMap();
    private transient volatile Set classConfiguration;
    private transient volatile Boolean constructorValidated;

    @Inject
    private Validator validator;

    @Inject
    private BValExtension globalConfiguration;

    private transient volatile ExecutableValidator executableValidator;

    @AroundConstruct // TODO: see previous one
    public Object construct(InvocationContext context) throws Exception {
        @SuppressWarnings("rawtypes")
        final Constructor constructor = context.getConstructor();
        final Class targetClass = constructor.getDeclaringClass();
        if (!isConstructorValidated(targetClass, constructor)) {
            return context.proceed();
        }

        final ConstructorDescriptor constraints =
            validator.getConstraintsForClass(targetClass).getConstraintsForConstructor(constructor.getParameterTypes());
        if (constraints == null) { // surely implicit constructor
            return context.proceed();
        }

        initExecutableValidator();

        {
            @SuppressWarnings("unchecked")
            final Set> violations =
                executableValidator.validateConstructorParameters(constructor, context.getParameters());
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }

        final Object result = context.proceed();

        {
            @SuppressWarnings("unchecked")
            final Set> violations =
                executableValidator.validateConstructorReturnValue(constructor, context.getTarget());
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }

        return result;
    }

    @AroundInvoke
    public Object invoke(final InvocationContext context) throws Exception {
        final Method method = context.getMethod();
        final Class targetClass = Proxies.classFor(context.getTarget().getClass());
        if (!isMethodValidated(targetClass, method)) {
            return context.proceed();
        }

        final MethodDescriptor constraintsForMethod = validator.getConstraintsForClass(targetClass)
            .getConstraintsForMethod(method.getName(), method.getParameterTypes());
        if (constraintsForMethod == null) {
            return context.proceed();
        }

        initExecutableValidator();

        {
            final Set> violations =
                executableValidator.validateParameters(context.getTarget(), method, context.getParameters());
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }

        final Object result = context.proceed();

        {
            final Set> violations =
                executableValidator.validateReturnValue(context.getTarget(), method, result);
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }

        return result;
    }

    private boolean isConstructorValidated(final Class targetClass, final Constructor constructor)
        throws NoSuchMethodException {
        initClassConfig(targetClass);

        if (constructorValidated == null) {
            synchronized (this) {
                if (constructorValidated == null) {
                    final AnnotatedType annotatedType =
                        CDI.current().getBeanManager().createAnnotatedType(constructor.getDeclaringClass());
                    AnnotatedConstructor annotatedConstructor = null;
                    for (final AnnotatedConstructor ac : annotatedType.getConstructors()) {
                        if (!constructor.equals(ac.getJavaMember())) {
                            continue;
                        }
                        annotatedConstructor = ac;
                        break;
                    }
                    final ValidateOnExecution annotation = annotatedConstructor != null
                        ? annotatedConstructor.getAnnotation(ValidateOnExecution.class) : targetClass
                            .getConstructor(constructor.getParameterTypes()).getAnnotation(ValidateOnExecution.class);
                    if (annotation == null) {
                        constructorValidated = classConfiguration.contains(ExecutableType.CONSTRUCTORS);
                    } else {
                        final Collection types = Arrays.asList(annotation.type());
                        constructorValidated = types.contains(ExecutableType.CONSTRUCTORS)
                            || types.contains(ExecutableType.IMPLICIT) || types.contains(ExecutableType.ALL);
                    }
                }
            }
        }

        return constructorValidated;
    }

    private boolean isMethodValidated(final Class targetClass, final Method method) throws NoSuchMethodException {
        initClassConfig(targetClass);

        if (methodConfiguration == null) {
            synchronized (this) {
                if (methodConfiguration == null) {
                    methodConfiguration = new ConcurrentHashMap();
                }
            }
        }

        Boolean methodConfig = methodConfiguration.get(method);
        if (methodConfig == null) {
            synchronized (this) {
                methodConfig = methodConfiguration.get(method);
                if (methodConfig == null) {
                    final List> classHierarchy =
                        ClassHelper.fillFullClassHierarchyAsList(new LinkedList>(), targetClass);
                    Collections.reverse(classHierarchy);

                    // search on method @ValidateOnExecution
                    ValidateOnExecution validateOnExecution = null;
                    ValidateOnExecution validateOnExecutionType = null;
                    for (final Class c : classHierarchy) {
                        final AnnotatedType annotatedType = CDI.current().getBeanManager().createAnnotatedType(c);
                        AnnotatedMethod annotatedMethod = null;
                        for (final AnnotatedMethod m : annotatedType.getMethods()) {
                            if (!m.getJavaMember().getName().equals(method.getName())
                                || !asList(method.getGenericParameterTypes())
                                    .equals(asList(m.getJavaMember().getGenericParameterTypes()))) {
                                continue;
                            }
                            annotatedMethod = m;
                            break;
                        }
                        try {
                            if (annotatedMethod == null) {
                                continue;
                            }
                            if (validateOnExecutionType == null) {
                                final ValidateOnExecution vat = annotatedType.getAnnotation(ValidateOnExecution.class);
                                if (vat != null) {
                                    validateOnExecutionType = vat;
                                }
                            }
                            final ValidateOnExecution mvat = annotatedMethod.getAnnotation(ValidateOnExecution.class);
                            if (mvat != null) {
                                validateOnExecution = mvat;
                            }
                        } catch (final Throwable h) {
                            // no-op
                        }
                    }

                    // if not found look in the class declaring the method
                    boolean classMeta = false;
                    if (validateOnExecution == null) {
                        validateOnExecution = validateOnExecutionType;
                        classMeta = validateOnExecution != null;
                    }

                    if (validateOnExecution == null) {
                        methodConfig = doValidMethod(method, classConfiguration);
                    } else {
                        final Set config = EnumSet.noneOf(ExecutableType.class);
                        for (final ExecutableType type : validateOnExecution.type()) {
                            if (ExecutableType.NONE == type) {
                                continue;
                            }
                            if (ExecutableType.ALL == type) {
                                config.add(ExecutableType.NON_GETTER_METHODS);
                                config.add(ExecutableType.GETTER_METHODS);
                                break;
                            }
                            if (ExecutableType.IMPLICIT == type) { // on method it just means validate, even on getters
                                config.add(ExecutableType.NON_GETTER_METHODS);
                                if (!classMeta) {
                                    config.add(ExecutableType.GETTER_METHODS);
                                } // else the annotation was not on the method so implicit doesn't mean getters
                            } else {
                                config.add(type);
                            }
                        }
                        methodConfig = doValidMethod(method, config);
                    }
                }
                methodConfiguration.put(method, methodConfig);
            }
        }

        return methodConfig;
    }

    private void initClassConfig(Class targetClass) {
        if (classConfiguration == null) {
            synchronized (this) {
                if (classConfiguration == null) {
                    classConfiguration = EnumSet.noneOf(ExecutableType.class);

                    final AnnotatedType annotatedType =
                        CDI.current().getBeanManager().createAnnotatedType(targetClass);
                    final ValidateOnExecution annotation = annotatedType.getAnnotation(ValidateOnExecution.class);
                    if (annotation == null) {
                        classConfiguration.addAll(globalConfiguration.getGlobalExecutableTypes());
                    } else {
                        for (final ExecutableType type : annotation.type()) {
                            if (ExecutableType.NONE == type) {
                                continue;
                            }
                            if (ExecutableType.ALL == type) {
                                classConfiguration.add(ExecutableType.CONSTRUCTORS);
                                classConfiguration.add(ExecutableType.NON_GETTER_METHODS);
                                classConfiguration.add(ExecutableType.GETTER_METHODS);
                                break;
                            }
                            if (ExecutableType.IMPLICIT == type) {
                                classConfiguration.add(ExecutableType.CONSTRUCTORS);
                                classConfiguration.add(ExecutableType.NON_GETTER_METHODS);
                            } else {
                                classConfiguration.add(type);
                            }
                        }
                    }
                }
            }
        }
    }

    private void initExecutableValidator() {
        if (executableValidator == null) {
            synchronized (this) {
                if (executableValidator == null) {
                    executableValidator = validator.forExecutables();
                }
            }
        }
    }

    private static boolean doValidMethod(final Method method, final Set config) {
        return isGetter(method) ? config.contains(ExecutableType.GETTER_METHODS)
            : config.contains(ExecutableType.NON_GETTER_METHODS);
    }

    private static boolean isGetter(final Method method) {
        final String name = method.getName();
        return method.getParameterTypes().length == 0 && !Void.TYPE.equals(method.getReturnType())
            && (name.startsWith("get") || name.startsWith("is") && boolean.class.equals(method.getReturnType()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy