BeanHelper.java

/*
 * Copyright (c) 2017, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.inject.cdi.se.bean;

import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.InjectionTargetFactory;

import org.glassfish.jersey.inject.cdi.se.injector.CachedConstructorAnalyzer;
import org.glassfish.jersey.inject.cdi.se.injector.InjectionUtils;
import org.glassfish.jersey.inject.cdi.se.injector.JerseyConstructorInjectionPoint;
import org.glassfish.jersey.inject.cdi.se.injector.JerseyInjectionTarget;
import org.glassfish.jersey.inject.cdi.se.injector.WrappingJerseyInjectionTarget;
import org.glassfish.jersey.internal.inject.ClassBinding;
import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.PerThread;
import org.glassfish.jersey.internal.inject.SupplierClassBinding;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;

import org.jboss.weld.annotated.enhanced.EnhancedAnnotatedConstructor;
import org.jboss.weld.annotated.enhanced.EnhancedAnnotatedType;
import org.jboss.weld.annotated.enhanced.jlr.ConstructorSignatureImpl;
import org.jboss.weld.annotated.enhanced.jlr.EnhancedAnnotatedTypeImpl;
import org.jboss.weld.annotated.slim.SlimAnnotatedType;
import org.jboss.weld.bean.builtin.BeanManagerProxy;
import org.jboss.weld.injection.ConstructorInjectionPoint;
import org.jboss.weld.injection.producer.AbstractInstantiator;
import org.jboss.weld.injection.producer.BasicInjectionTarget;
import org.jboss.weld.injection.producer.BeanInjectionTarget;
import org.jboss.weld.injection.producer.InjectionTargetService;
import org.jboss.weld.injection.producer.Instantiator;
import org.jboss.weld.injection.producer.NonProducibleInjectionTarget;
import org.jboss.weld.manager.BeanManagerImpl;
import org.jboss.weld.resources.ClassTransformer;

/**
 * Helper class to register a {@link Bean} into CDI {@link BeanManager}.
 *
 * @author Petr Bouda
 */
public class BeanHelper {

    /**
     * Forbids the creation of {@link BeanHelper} instance.
     */
    private BeanHelper() {
    }

    /**
     * Registers an instance as {@link JerseyBean} into {@link BeanManager}.
     *
     * @param binding   object containing {@link javax.enterprise.inject.spi.BeanAttributes} information.
     * @param abd       {@link AfterBeanDiscovery} event.
     * @param resolvers all registered injection resolvers.
     * @param <T>       type of the instance which is registered.
     */
    public static <T> void registerBean(InstanceBinding<T> binding, AfterBeanDiscovery abd, List<InjectionResolver> resolvers) {
        InstanceBean<T> bean = new InstanceBean<>(binding);
        /*
         * Wrap into custom injection target that is able to inject the additional @Inject, @Context, @*Param fields into
         * the given service.
         */
        InjectionTarget<T> injectionTarget = new WrappingJerseyInjectionTarget<>(bean, resolvers);
        bean.setInjectionTarget(injectionTarget);
        abd.addBean(bean);
    }

    /**
     * Registers a class as {@link JerseyBean} into {@link BeanManager}.
     *
     * @param binding     object containing {@link javax.enterprise.inject.spi.BeanAttributes} information.
     * @param abd         {@link AfterBeanDiscovery} event.
     * @param resolvers   all registered injection resolvers.
     * @param beanManager currently used bean manager.
     * @param <T>         type of the class which is registered.
     */
    public static <T> void registerBean(ClassBinding<T> binding, AfterBeanDiscovery abd, Collection<InjectionResolver> resolvers,
            BeanManager beanManager) {
        AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(binding.getService());
        InjectionTargetFactory<T> injectionTargetFactory = beanManager.getInjectionTargetFactory(annotatedType);
        InjectionTarget<T> injectionTarget = injectionTargetFactory.createInjectionTarget(null);

        ClassBean<T> bean = new ClassBean<>(binding);
        bean.setInjectionTarget(getJerseyInjectionTarget(binding.getService(), injectionTarget, bean, resolvers));
        abd.addBean(bean);
    }

    /**
     * Registers an instance supplier and its provided value as {@link JerseyBean}s into {@link BeanManager}.
     *
     * @param binding object containing {@link javax.enterprise.inject.spi.BeanAttributes} information.
     * @param abd     {@link AfterBeanDiscovery} event.
     * @param <T>     type of the instance which is registered.
     */
    public static <T> void registerSupplier(SupplierInstanceBinding<T> binding, AfterBeanDiscovery abd, BeanManager beanManager) {
        /*
         * CDI does not provide sufficient support for ThreadScoped Supplier
         */
        if (binding.getScope() == PerThread.class) {
            abd.addBean(new SupplierThreadScopeBean(binding, beanManagerImpl(beanManager)));
        } else {
            abd.addBean(new SupplierInstanceBean<>(binding));
            abd.addBean(new SupplierInstanceBeanBridge<>(binding));
        }
    }

    /**
     * Registers a class supplier and its provided value as {@link JerseyBean}s into {@link BeanManager}.
     *
     * @param binding     object containing {@link javax.enterprise.inject.spi.BeanAttributes} information.
     * @param abd         {@link AfterBeanDiscovery} event.
     * @param resolvers   all registered injection resolvers.
     * @param beanManager currently used bean manager.
     * @param <T>         type of the class which is registered.
     */
    @SuppressWarnings("unchecked")
    public static <T> void registerSupplier(SupplierClassBinding<T> binding, AfterBeanDiscovery abd,
            Collection<InjectionResolver> resolvers, BeanManager beanManager) {

        Class<Supplier<T>> supplierClass = (Class<Supplier<T>>) binding.getSupplierClass();
        AnnotatedType<Supplier<T>> annotatedType = beanManager.createAnnotatedType(supplierClass);
        InjectionTarget<Supplier<T>> injectionTarget = beanManager.createInjectionTarget(annotatedType);

        SupplierClassBean<T> supplierBean = new SupplierClassBean<>(binding);
        InjectionTarget<Supplier<T>> jit = getJerseyInjectionTarget(supplierClass, injectionTarget, supplierBean, resolvers);
        supplierBean.setInjectionTarget(jit);

        /*
         * CDI does not provide sufficient support for ThreadScoped Supplier
         */
        if (binding.getScope() == PerThread.class) {
            abd.addBean(new SupplierThreadScopeClassBean(binding, supplierBean, beanManagerImpl(beanManager)));
        } else {
            abd.addBean(supplierBean);
            abd.addBean(new SupplierBeanBridge(binding, beanManager));
        }
    }

    private static BeanManagerImpl beanManagerImpl(BeanManager beanManager) {
        if (beanManager instanceof BeanManagerProxy) {
            return ((BeanManagerProxy) beanManager).unwrap();
        } else {
            return (BeanManagerImpl) beanManager;
        }
    }

    private static <T> InjectionTarget<T> getJerseyInjectionTarget(Class<T> clazz, InjectionTarget<T> injectionTarget,
            Bean<T> bean, Collection<InjectionResolver> resolvers) {
        BasicInjectionTarget<T> it = (BasicInjectionTarget<T>) injectionTarget;

        /*
         * Looks at whether the DefaultInstantiator resolving a valid constructor does not met this case:
         * - No constructor with @Inject annotation is defined
         * - NoArgs constructor is defined
         * - Instantiator ignores JAX-RS valid constructor with multiple params
         */
        boolean noArgConstructor = isNoArgConstructorCase(it, clazz);

        JerseyInjectionTarget<T> jit;
        /*
         * CDI is able to find a constructor that means that the class contains only one constructor of this type:
         * - default constructor
         * - non-argument constructor
         * - multi-param constructor annotated by @Inject annotation and able to inject all parameters.
         */
        if (!noArgConstructor && injectionTarget instanceof BeanInjectionTarget) {
            jit = new JerseyInjectionTarget<>(it, bean, clazz, resolvers);

        /*
         * CDI failed during the looking for a proper constructor because of these reasons:
         * - multi-param constructor not annotated by @Inject annotation
         * - multiple constructors annotated by @Inject annotation
         * - is not able to satisfied single constructor annotated by @Inject annotation
         *
         * Therefore produced NonProducibleInjectionTarget cannot create and instance, we try to find the proper constructor
         * using JAX-RS rules:
         * - largest constructor with all annotated parameters
         *
         * If JAX-RS valid constructor is not find - InjectionException is thrown
         */
        } else if (noArgConstructor || injectionTarget instanceof NonProducibleInjectionTarget) {
            CachedConstructorAnalyzer<T> analyzer =
                    new CachedConstructorAnalyzer<>(clazz, InjectionUtils.getInjectAnnotations(resolvers));

            /*
             * Contains the analyzed class any constructor that can be injected by Jersey?
             */
            if (analyzer.hasCompatibleConstructor()) {
                EnhancedAnnotatedConstructor<T> constructor = createEnhancedAnnotatedType(it)
                        .getDeclaredEnhancedConstructor(new ConstructorSignatureImpl(analyzer.getConstructor()));

                JerseyConstructorInjectionPoint<T> constructorInjectionPoint =
                        new JerseyConstructorInjectionPoint<>(constructor, bean, it.getBeanManager(), resolvers);

                Instantiator<T> instantiator = new JerseyInstantiator<>(constructorInjectionPoint);
                jit = new JerseyInjectionTarget<>(createEnhancedAnnotatedType(it), it, bean, clazz, resolvers, instantiator);

            /*
             * Instance of this class cannot be created neither CDI nor Jersey therefore mark it as non-producible.
             */
            } else {
                return new WrappingJerseyInjectionTarget<>(it, bean, resolvers);
            }
        } else {
            throw new RuntimeException("Unknown InjectionTarget for the class: " + clazz.getTypeName());
        }

        InjectionTargetService injectionTargetService = it.getBeanManager().getServices().get(InjectionTargetService.class);
        injectionTargetService.addInjectionTargetToBeInitialized(jit.getEnhancedAnnotatedType(), jit);
        return jit;
    }

    public static <T> EnhancedAnnotatedType<T> createEnhancedAnnotatedType(BasicInjectionTarget<T> it) {
        return EnhancedAnnotatedTypeImpl.of(
                (SlimAnnotatedType<T>) it.getAnnotatedType(), ClassTransformer.instance(it.getBeanManager()));
    }

    /**
     * Looks at whether the DefaultInstantiator resolving a valid constructor does not met this case:
     * - No constructor with @Inject annotation is defined
     * - NoArgs constructor is defined
     * - Instantiator ignores JAX-RS valid constructor with multiple params
     *
     * @param it    injection target containing instantiator with resolved constructor.
     * @param clazz class which analyzed constructor belongs to.
     * @param <T>   type of the analyzed class.
     * @return {@code true} if no-arg constructor was selected while multi-params constructor exists.
     */
    private static <T> boolean isNoArgConstructorCase(BasicInjectionTarget<T> it, Class<T> clazz) {
        if (!(it instanceof NonProducibleInjectionTarget)) {
            Instantiator<T> instantiator = it.getInstantiator();
            Constructor<T> constructor = instantiator.getConstructor();
            return constructor.getParameterCount() == 0 && clazz.getConstructors().length > 1;
        }

        return false;
    }

    /**
     * Wrapper class to provide Jersey implementation of {@link Instantiator} interface.
     *
     * @param <T> type which is created by instantiator.
     */
    private static class JerseyInstantiator<T> extends AbstractInstantiator<T> {

        private final ConstructorInjectionPoint<T> injectionPoint;

        private JerseyInstantiator(ConstructorInjectionPoint<T> injectionPoint) {
            this.injectionPoint = injectionPoint;
        }

        @Override
        public ConstructorInjectionPoint<T> getConstructorInjectionPoint() {
            return injectionPoint;
        }

        @Override
        public Constructor<T> getConstructor() {
            return injectionPoint.getAnnotated().getJavaMember();
        }

        @Override
        public String toString() {
            return "JerseyInstantiator [constructor=" + injectionPoint.getMember() + "]";
        }

        @Override
        public boolean hasInterceptorSupport() {
            return false;
        }

        @Override
        public boolean hasDecoratorSupport() {
            return false;
        }
    }
}