CdiComponentProviderServerRuntimeSpecifics.java

/*
 * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2022 Payara Foundation 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.ext.cdi1x.internal;

import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.collection.Cache;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Qualifier;
import javax.ws.rs.core.Context;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.util.Enumeration;

/**
 * Server side runtime CDI ComponentProvider specific implementation.
 */
class CdiComponentProviderServerRuntimeSpecifics implements CdiComponentProviderRuntimeSpecifics {
    /*
     * annotation types that distinguish the classes to be added to {@link CdiComponentProvider#jaxrsInjectableTypes}
     */
    private static final Set<Class<? extends Annotation>> JAX_RS_INJECT_ANNOTATIONS =
            new HashSet<Class<? extends Annotation>>() {{
                addAll(JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
                add(Context.class);
            }};


    // Check first if a class is a JAX-RS resource, and only if so check with validation.
    // This prevents unnecessary warnings being logged for pure CDI beans.
    private final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(
            clazz -> Resource.from(clazz, true) != null && Resource.from(clazz) != null);

    /**
     * CDI producer for CDI bean constructor String parameters, that should be injected by JAX-RS.
     */
    @ApplicationScoped
    public static class JaxRsParamProducer {

        @Qualifier
        @Retention(RUNTIME)
        @Target({METHOD, FIELD, PARAMETER, TYPE})
        public static @interface JaxRsParamQualifier {
        }

        private static final JaxRsParamQualifier JaxRsParamQUALIFIER = new JaxRsParamQualifier() {

            @Override
            public Class<? extends Annotation> annotationType() {
                return JaxRsParamQualifier.class;
            }
        };

        static final Set<Class<? extends Annotation>> JAX_RS_STRING_PARAM_ANNOTATIONS =
                new HashSet<Class<? extends Annotation>>() {{
                    add(javax.ws.rs.PathParam.class);
                    add(javax.ws.rs.QueryParam.class);
                    add(javax.ws.rs.CookieParam.class);
                    add(javax.ws.rs.HeaderParam.class);
                    add(javax.ws.rs.MatrixParam.class);
                    add(javax.ws.rs.FormParam.class);
                }};

        /**
         * Internal cache to store CDI {@link InjectionPoint} to Jersey {@link Parameter} mapping.
         */
        final Cache<InjectionPoint, Parameter> parameterCache = new Cache<>(injectionPoint -> {
            final Annotated annotated = injectionPoint.getAnnotated();
            final Class<?> clazz = injectionPoint.getMember().getDeclaringClass();

            if (annotated instanceof AnnotatedParameter) {

                final AnnotatedParameter annotatedParameter = (AnnotatedParameter) annotated;
                final AnnotatedCallable callable = annotatedParameter.getDeclaringCallable();

                if (callable instanceof AnnotatedConstructor) {

                    final AnnotatedConstructor ac = (AnnotatedConstructor) callable;
                    final int position = annotatedParameter.getPosition();
                    final List<Parameter> parameters = Parameter.create(clazz, clazz, ac.getJavaMember(), false);

                    return parameters.get(position);
                }
            }

            return null;
        });

        /**
         * Provide a value for given injection point. If the injection point does not refer
         * to a CDI bean constructor parameter, or the value could not be found, the method will return null.
         *
         * @param injectionPoint actual injection point.
         * @param beanManager    current application bean manager.
         * @return concrete JAX-RS parameter value for given injection point.
         */
        @javax.enterprise.inject.Produces
        @JaxRsParamQualifier
        public String getParameterValue(final InjectionPoint injectionPoint, final BeanManager beanManager) {
            final Parameter parameter = parameterCache.apply(injectionPoint);

            if (parameter != null) {
                InjectionManager injectionManager =
                        beanManager.getExtension(CdiComponentProvider.class).getEffectiveInjectionManager();

                Set<ValueParamProvider> providers = Providers.getProviders(injectionManager, ValueParamProvider.class);
                ContainerRequest containerRequest = injectionManager.getInstance(ContainerRequest.class);
                for (ValueParamProvider vfp : providers) {
                    Function<ContainerRequest, ?> paramValueSupplier = vfp.getValueProvider(parameter);
                    if (paramValueSupplier != null) {
                        return (String) paramValueSupplier.apply(containerRequest);
                    }
                }
            }

            return null;
        }
    }

    @Override
    public AnnotatedParameter<?> getAnnotatedParameter(AnnotatedParameter<?> ap) {
        return new AnnotatedParameter() {

            @Override
            public int getPosition() {
                return ap.getPosition();
            }

            @Override
            public AnnotatedCallable getDeclaringCallable() {
                return ap.getDeclaringCallable();
            }

            @Override
            public Type getBaseType() {
                return ap.getBaseType();
            }

            @Override
            public Set<Type> getTypeClosure() {
                return ap.getTypeClosure();
            }

            @Override
            public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
                if (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class) {
                    return CdiComponentProvider.hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS)
                            ? (T) JaxRsParamProducer.JaxRsParamQUALIFIER : null;
                } else {
                    return ap.getAnnotation(annotationType);
                }
            }

            @Override
            public Set<Annotation> getAnnotations() {
                final Set<Annotation> result = new HashSet<>();
                for (final Annotation a : ap.getAnnotations()) {
                    result.add(a);
                    final Class<? extends Annotation> annotationType = a.annotationType();
                    if (JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS.contains(annotationType)) {
                        result.add(JaxRsParamProducer.JaxRsParamQUALIFIER);
                    }
                }
                return result;
            }

            @Override
            public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
                return (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class
                        && CdiComponentProvider.hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS))
                        || ap.isAnnotationPresent(annotationType);
            }
        };
    }

    @Override
    public Set<Class<? extends Annotation>> getJaxRsInjectAnnotations() {
        return JAX_RS_INJECT_ANNOTATIONS;
    }

    @Override
    public boolean isAcceptableResource(Class<?> resource) {
        return Resource.isAcceptable(resource);
    }

    @Override
    public boolean isJaxRsResource(Class<?> resource) {
        return jaxRsResourceCache.apply(resource);
    }

    @Override
    public void clearJaxRsResource(ClassLoader loader) {
        Enumeration<Class<?>> keys = jaxRsResourceCache.keys();
        while (keys.hasMoreElements()) {
            Class<?> key = keys.nextElement();
            if (key.getClassLoader() == loader) {
                jaxRsResourceCache.remove(key);
            }
        }
    }

    @Override
    public boolean containsJaxRsParameterizedCtor(final AnnotatedType annotatedType) {
        return CdiComponentProvider
                .containAnnotatedParameters(annotatedType.getConstructors(), JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
    }
}