org.apache.bval.cdi.BValExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.bval.cdi.BValExtension.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.cdi;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessBean;
import javax.validation.BootstrapConfiguration;
import javax.validation.Configuration;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableType;
import javax.validation.executable.ValidateOnExecution;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.MethodType;

import org.apache.commons.lang3.Validate;

import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * CDI {@link Extension} for Apache BVal setup.
 */
public class BValExtension implements Extension {
    private static final Logger LOGGER = Logger.getLogger(BValExtension.class.getName());

    private static final AnnotatedTypeFilter DEFAULT_ANNOTATED_TYPE_FILTER = new AnnotatedTypeFilter() {

        @Override
        public boolean accept(AnnotatedType<?> annotatedType) {
            return !annotatedType.getJavaClass().getName().startsWith("org.apache.bval.");
        }
    };

    private static AnnotatedTypeFilter annotatedTypeFilter = DEFAULT_ANNOTATED_TYPE_FILTER;

    public static void setAnnotatedTypeFilter(AnnotatedTypeFilter annotatedTypeFilter) {
        BValExtension.annotatedTypeFilter = Validate.notNull(annotatedTypeFilter);
    }

    private boolean validatorFound = Boolean.getBoolean("bval.in-container");
    private boolean validatorFactoryFound = Boolean.getBoolean("bval.in-container");

    private boolean validBean;
    private boolean validConstructors;
    private boolean validBusinessMethods;
    private boolean validGetterMethods;

    private final Configuration<?> config;
    private ValidatorFactory factory;
    private Validator validator;

    private Set<ExecutableType> globalExecutableTypes;
    private boolean isExecutableValidationEnabled;

    public BValExtension() { // read the config, could be done in a quicker way but this let us get defaults without duplicating code
        config = Validation.byDefaultProvider().configure();
        try {
            final BootstrapConfiguration bootstrap = config.getBootstrapConfiguration();
            globalExecutableTypes = Collections
                    .unmodifiableSet(convertToRuntimeTypes(bootstrap.getDefaultValidatedExecutableTypes()));
            isExecutableValidationEnabled = bootstrap.isExecutableValidationEnabled();

            // TODO we never contain IMPLICIT or ALL
            validBean = globalExecutableTypes.contains(ExecutableType.IMPLICIT)
                    || globalExecutableTypes.contains(ExecutableType.ALL);
            validConstructors = validBean || globalExecutableTypes.contains(ExecutableType.CONSTRUCTORS);
            validBusinessMethods = validBean || globalExecutableTypes.contains(ExecutableType.NON_GETTER_METHODS);
            validGetterMethods = globalExecutableTypes.contains(ExecutableType.ALL)
                    || globalExecutableTypes.contains(ExecutableType.GETTER_METHODS);
        } catch (final Exception e) { // custom providers can throw an exception
            LOGGER.log(Level.SEVERE, e.getMessage(), e);

            globalExecutableTypes = Collections.emptySet();
            isExecutableValidationEnabled = false;
        }
    }

    // lazily to get a small luck to have CDI in place
    private void ensureFactoryValidator() {
        if (validator != null) {
            return;
        }
        config.addProperty("bval.before.cdi", "true"); // ignore parts of the config relying on CDI since we didn't start yet
        if (factory == null) {
            factory = config.buildValidatorFactory();
        }
        validator = factory.getValidator();
    }

    private static Set<ExecutableType> convertToRuntimeTypes(
            final Set<ExecutableType> defaultValidatedExecutableTypes) {
        final Set<ExecutableType> types = EnumSet.noneOf(ExecutableType.class);
        for (final ExecutableType type : defaultValidatedExecutableTypes) {
            if (ExecutableType.NONE == type) {
                continue;
            }
            if (ExecutableType.ALL == type) {
                types.add(ExecutableType.CONSTRUCTORS);
                types.add(ExecutableType.NON_GETTER_METHODS);
                types.add(ExecutableType.GETTER_METHODS);
                break;
            }
            if (ExecutableType.IMPLICIT == type) {
                types.add(ExecutableType.CONSTRUCTORS);
                types.add(ExecutableType.NON_GETTER_METHODS);
            } else {
                types.add(type);
            }
        }
        return types;
    }

    public Set<ExecutableType> getGlobalExecutableTypes() {
        return globalExecutableTypes;
    }

    public void addBvalBinding(final @Observes BeforeBeanDiscovery beforeBeanDiscovery,
            final BeanManager beanManager) {
        beforeBeanDiscovery.addInterceptorBinding(BValBinding.class);
        beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(BValInterceptor.class));
    }

    // @WithAnnotations(ValidateOnExecution.class) doesn't check interfaces so not enough
    public <A> void processAnnotatedType(final @Observes ProcessAnnotatedType<A> pat) {
        if (!isExecutableValidationEnabled) {
            return;
        }

        final AnnotatedType<A> annotatedType = pat.getAnnotatedType();

        if (!annotatedTypeFilter.accept(annotatedType)) {
            return;
        }

        final Class<A> javaClass = annotatedType.getJavaClass();
        final int modifiers = javaClass.getModifiers();
        if (!javaClass.isInterface() && !Modifier.isFinal(modifiers) && !Modifier.isAbstract(modifiers)) {
            try {
                ensureFactoryValidator();
                try {
                    final BeanDescriptor classConstraints = validator.getConstraintsForClass(javaClass);
                    if (annotatedType.isAnnotationPresent(ValidateOnExecution.class)
                            || hasValidationAnnotation(annotatedType.getMethods())
                            || hasValidationAnnotation(annotatedType.getConstructors())
                            || classConstraints != null && (validBean && classConstraints.isBeanConstrained()
                                    || validConstructors && !classConstraints.getConstrainedConstructors().isEmpty()
                                    || validBusinessMethods && !classConstraints
                                            .getConstrainedMethods(MethodType.NON_GETTER).isEmpty()
                                    || validGetterMethods && !classConstraints
                                            .getConstrainedMethods(MethodType.GETTER).isEmpty())) {
                        final BValAnnotatedType<A> bValAnnotatedType = new BValAnnotatedType<A>(annotatedType);
                        pat.setAnnotatedType(bValAnnotatedType);
                    }
                } catch (final NoClassDefFoundError ncdfe) {
                    // skip
                }
            } catch (final ValidationException ve) {
                LOGGER.log(Level.FINEST, ve.getMessage(), ve);
            } catch (final Exception e) { // just info
                LOGGER.log(Level.INFO, e.getMessage());
            }
        }
    }

    private static <A> boolean hasValidationAnnotation(
            final Collection<? extends AnnotatedCallable<? super A>> methods) {
        for (final AnnotatedCallable<? super A> m : methods) {
            if (m.isAnnotationPresent(ValidateOnExecution.class)) {
                return true;
            }
        }
        return false;
    }

    public <A> void processBean(final @Observes ProcessBean<A> processBeanEvent) {
        if (validatorFound && validatorFactoryFound) {
            return;
        }

        final Bean<A> bean = processBeanEvent.getBean();
        if (ValidatorBean.class.isInstance(bean) || ValidatorFactoryBean.class.isInstance(bean)) {
            return;
        }

        final Set<Type> types = bean.getTypes();
        if (!validatorFound) {
            validatorFound = types.contains(Validator.class);
        }
        if (!validatorFactoryFound) {
            validatorFactoryFound = types.contains(ValidatorFactory.class);
        }
    }

    public void addBValBeans(final @Observes AfterBeanDiscovery afterBeanDiscovery, final BeanManager beanManager) {
        if (factory != null) { // cleanup cache used to discover ValidateOnException before factory is recreated
            factory.close();
        }

        cdiIntegration(afterBeanDiscovery, beanManager);
    }

    private void cdiIntegration(final AfterBeanDiscovery afterBeanDiscovery, final BeanManager beanManager) {
        try {
            config.addProperty("bval.before.cdi", "false"); // now take into account all the config
        } catch (final Exception e) {
            // no-op: sadly tck does it
        }

        if (!validatorFactoryFound) {
            try { // recreate the factory
                afterBeanDiscovery.addBean(new ValidatorFactoryBean(factory = config.buildValidatorFactory()));
            } catch (final Exception e) { // can throw an exception with custom providers
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
            }
        }
        if (!validatorFound) {
            try {
                if (validatorFactoryFound) {
                    factory = config.buildValidatorFactory();
                } // else fresh factory already created in previous if
                afterBeanDiscovery.addBean(new ValidatorBean(factory, factory.getValidator()));
                validatorFound = true;
            } catch (final Exception e) { // getValidator can throw an exception with custom providers
                afterBeanDiscovery.addBean(new ValidatorBean(factory, null));
                validatorFound = true;
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
            }
        }
    }

    /**
     * Request that an instance of the specified type be provided by the container.
     * @param clazz
     * @return the requested instance wrapped in a {@link Releasable}.
     */
    public static <T> Releasable<T> inject(final Class<T> clazz) {
        try {
            final BeanManager beanManager = CDI.current().getBeanManager();
            if (beanManager == null) {
                return null;
            }
            final AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(clazz);
            final InjectionTarget<T> it = beanManager.createInjectionTarget(annotatedType);
            final CreationalContext<T> context = beanManager.createCreationalContext(null);
            final T instance = it.produce(context);
            it.inject(instance, context);
            it.postConstruct(instance);

            return new Releasable<T>(context, it, instance);
        } catch (final Exception e) {
            // no-op
        } catch (final NoClassDefFoundError error) {
            // no-op
        }
        return null;
    }

    public static BeanManager getBeanManager() {
        return CDI.current().getBeanManager();
    }

    /**
     * Represents an item that can be released from a {@link CreationalContext} at some point in the future.
     * @param <T>
     */
    public static class Releasable<T> {
        private final CreationalContext<T> context;
        private final InjectionTarget<T> injectionTarget;
        private final T instance;

        private Releasable(final CreationalContext<T> context, final InjectionTarget<T> injectionTarget,
                final T instance) {
            this.context = context;
            this.injectionTarget = injectionTarget;
            this.instance = instance;
        }

        public void release() {
            try {
                injectionTarget.preDestroy(instance);
                injectionTarget.dispose(instance);
                context.release();
            } catch (final Exception e) {
                // no-op
            } catch (final NoClassDefFoundError e) {
                // no-op
            }
        }

        public T getInstance() {
            return instance;
        }
    }

    /**
     * Defines an item that can determine whether a given {@link AnnotatedType} will be processed
     * by the {@link BValExtension} for executable validation. May be statically applied before
     * container startup.
     * @see BValExtension#setAnnotatedTypeFilter(AnnotatedTypeFilter)
     */
    public interface AnnotatedTypeFilter {
        boolean accept(AnnotatedType<?> annotatedType);
    }
}