ContextInjectionResolverImpl.java

/*
 * Copyright (c) 2021 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.weld.internal.injector;

import java.lang.reflect.Type;
import java.util.Set;
import java.util.function.Supplier;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericType;

import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.ContextInjectionResolver;
import org.glassfish.jersey.internal.inject.Injectee;
import org.glassfish.jersey.internal.inject.InjecteeImpl;
import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.Cache;

/**
 * Injection resolver for {@link Context @Context} injection annotation.
 *
 * @author Petr Bouda
 */
public class ContextInjectionResolverImpl implements InjectionResolver<Context>, ContextInjectionResolver {

    private Supplier<BeanManager> beanManager;

    /**
     * Creates a new {@link ContextInjectionResolver} with {@link BeanManager} to fetch Bean descriptors.
     *
     * @param beanManager current bean manager.
     */
    ContextInjectionResolverImpl(Supplier<BeanManager> beanManager) {
        this.beanManager = beanManager;
    }

    private final Cache<Type, Bean<?>> descriptorCache = new Cache<>(key -> {
        Set<Bean<?>> beans = beanManager.get().getBeans(key);
        if (beans.isEmpty()) {
            return null;
        }
        return beans.iterator().next();
    });

    @Override
    public Object resolve(Injectee injectee) {
        Injectee newInjectee = injectee;
        if (injectee.isFactory()) {
            newInjectee = getFactoryInjectee(injectee, ReflectionHelper.getTypeArgument(injectee.getRequiredType(), 0));
        }

        Bean<?> bean = descriptorCache.apply(newInjectee.getRequiredType());

        if (bean != null) {
            CreationalContext ctx = beanManager.get().createCreationalContext(bean);
            Object result = bean.create(ctx);

            if (injectee.isFactory()) {
                return (Supplier<Object>) () -> result;
            } else {
                return result;
            }
        }
        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() {
        return true;
    }

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

    @Override
    public Class<Context> getAnnotation() {
        return Context.class;
    }

    /**
     * Context injection resolver binder.
     */
    public static final class Binder extends AbstractBinder {

        private Supplier<BeanManager> beanManager;

        public Binder(Supplier<BeanManager> beanManager) {
            this.beanManager = beanManager;
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void configure() {
            ContextInjectionResolverImpl resolver = new ContextInjectionResolverImpl(beanManager);

            /*
             * Binding for CDI, without this binding JerseyInjectionTarget wouldn't know about the ContextInjectionTarget and
             * injection into fields would be disabled.
             */
            bind(resolver)
                    .to(new GenericType<InjectionResolver<Context>>() {})
                    .to(ContextInjectionResolver.class);

            /*
             * Binding for Jersey, without this binding Jersey wouldn't put together ContextInjectionResolver and
             * DelegatedInjectionValueParamProvider and therefore injection into resource method would be disabled.
             */
            bind(Bindings.service(resolver))
                    .to(new GenericType<InjectionResolver<Context>>() {})
                    .to(ContextInjectionResolver.class);
        }
    }

    private Injectee getFactoryInjectee(Injectee injectee, Type requiredType) {
        return new RequiredTypeOverridingInjectee(injectee, requiredType);
    }

    private static class RequiredTypeOverridingInjectee extends InjecteeImpl {
        private RequiredTypeOverridingInjectee(Injectee injectee, Type requiredType) {
            setFactory(injectee.isFactory());
            setInjecteeClass(injectee.getInjecteeClass());
            setInjecteeDescriptor(injectee.getInjecteeDescriptor());
            setOptional(injectee.isOptional());
            setParent(injectee.getParent());
            setPosition(injectee.getPosition());
            setRequiredQualifiers(injectee.getRequiredQualifiers());
            setRequiredType(requiredType);
        }
    }
}