blob: 81dc831889dc5c66fe7d4f89be6848ffaf30b9cd [file] [log] [blame]
/*
* 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.bean;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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 javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.RuntimeType;
import org.glassfish.jersey.inject.weld.internal.data.BindingBeanPair;
import org.glassfish.jersey.inject.weld.internal.inject.InitializableInstanceBinding;
import org.glassfish.jersey.inject.weld.internal.inject.InitializableSupplierInstanceBinding;
import org.glassfish.jersey.inject.weld.internal.injector.CachedConstructorAnalyzer;
import org.glassfish.jersey.inject.weld.internal.injector.InjectionUtils;
import org.glassfish.jersey.inject.weld.internal.injector.JerseyConstructorInjectionPoint;
import org.glassfish.jersey.inject.weld.internal.injector.JerseyInjectionTarget;
import org.glassfish.jersey.inject.weld.internal.injector.JerseyTwofoldInstantiator;
import org.glassfish.jersey.inject.weld.internal.injector.WrappingJerseyInjectionTarget;
import org.glassfish.jersey.internal.inject.ClassBinding;
import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.inject.PerThread;
import org.glassfish.jersey.internal.inject.SupplierClassBinding;
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}.
*/
public abstract 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(RuntimeType runtimeType, InitializableInstanceBinding<T> binding, AfterBeanDiscovery abd,
List<InjectionResolver> resolvers, BeanManager beanManager) {
InitializableInstanceBean<T> bean = new InitializableInstanceBean<>(runtimeType, 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> BindingBeanPair registerBean(RuntimeType runtimeType, 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<>(runtimeType, binding);
bean.setInjectionTarget(getJerseyInjectionTarget(binding.getService(), injectionTarget, bean, resolvers));
abd.addBean(bean);
return new BindingBeanPair(binding, 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(RuntimeType runtimeType, InitializableSupplierInstanceBinding<T> binding,
AfterBeanDiscovery abd, BeanManager beanManager) {
/*
* CDI does not provide sufficient support for ThreadScoped Supplier
*/
if (binding.getScope() == PerThread.class) {
BeanManagerImpl manager;
if (beanManager instanceof BeanManagerProxy) {
manager = ((BeanManagerProxy) beanManager).unwrap();
} else {
manager = (BeanManagerImpl) beanManager;
}
abd.addBean(new InitializableSupplierThreadScopeBean(runtimeType, binding, manager));
} else {
abd.addBean(new InitializableSupplierInstanceBean<>(runtimeType, binding));
abd.addBean(new InitializableSupplierInstanceBeanBridge<>(runtimeType, 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> BindingBeanPair registerSupplier(RuntimeType runtimeType, 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<>(runtimeType, binding);
InjectionTarget<Supplier<T>> jit = getJerseyInjectionTarget(supplierClass, injectionTarget, supplierBean, resolvers);
supplierBean.setInjectionTarget(jit);
final SupplierBeanBridge supplierBeanBridge = new SupplierBeanBridge(runtimeType, binding, beanManager);
abd.addBean(supplierBean);
abd.addBean(supplierBeanBridge);
return new BindingBeanPair(binding, supplierBean, supplierBeanBridge);
}
/**
* Update ClassBinding Bean by {@link ConstructorInjectionPoint} for the client side beans.
* @param binding The ClassBinding used to create a client side ConstructorInjectionPoint.
* @param pair {@link BindingBeanPair} that contains the original server side Bean.
* @param resolvers Resolvers handling Jersey specific injection annotations.
* @param beanManager The {@link BeanManager}.
*/
public static void updateBean(ClassBinding binding,
BindingBeanPair pair, Collection<InjectionResolver> resolvers, BeanManager beanManager) {
final JerseyBean bean = pair.getBeans().get(0);
final ConstructorInjectionPoint cip = createConstructorInjectionPoint(binding, bean, resolvers, beanManager);
if (ClassBean.class.isInstance(bean)
&& JerseyInjectionTarget.class.isInstance(((ClassBean) bean).getInjectionTarget())) {
final JerseyTwofoldInstantiator instantiator =
((JerseyInjectionTarget) ((ClassBean) bean).getInjectionTarget()).getTwofoldInstantiator();
instantiator.setOptionalConstructorInjectionPoint(cip);
}
}
/**
* Update SupplierClassBinding Bean by {@link ConstructorInjectionPoint} for the client side beans.
* @param binding The SupplierClassBinding used to create a client side ConstructorInjectionPoint.
* @param pair {@link BindingBeanPair} that contains the original server side Bean.
* @param resolvers Resolvers handling Jersey specific injection annotations.
* @param beanManager The {@link BeanManager}.
*/
public static void updateSupplierBean(SupplierClassBinding binding,
BindingBeanPair pair, Collection<InjectionResolver> resolvers, BeanManager beanManager) {
final JerseyBean bean = pair.getBeans().get(0);
final ConstructorInjectionPoint cip = createConstructorInjectionPoint(binding, bean, resolvers, beanManager);
if (SupplierClassBean.class.isInstance(bean)
&& JerseyInjectionTarget.class.isInstance(((SupplierClassBean) bean).getInjectionTarget())) {
final JerseyTwofoldInstantiator instantiator =
((JerseyInjectionTarget) ((SupplierClassBean) bean).getInjectionTarget()).getTwofoldInstantiator();
instantiator.setOptionalConstructorInjectionPoint(cip);
}
}
private static <T> ConstructorInjectionPoint<T> createConstructorInjectionPoint(
SupplierClassBinding<T> binding, Bean<T> bean, Collection<InjectionResolver> resolvers, BeanManager beanManager) {
final Class<Supplier<T>> bindingClass = (Class<Supplier<T>>) binding.getSupplierClass();
final AnnotatedType<Supplier<T>> annotatedType = beanManager.createAnnotatedType(bindingClass);
final InjectionTarget<Supplier<T>> injectionTarget = beanManager.createInjectionTarget(annotatedType);
final CachedConstructorAnalyzer<Supplier<T>> analyzer =
new CachedConstructorAnalyzer<>(bindingClass, InjectionUtils.getInjectAnnotations(resolvers));
if (analyzer.hasCompatibleConstructor()) {
EnhancedAnnotatedConstructor<T> constructor = createEnhancedAnnotatedType((BasicInjectionTarget) injectionTarget)
.getDeclaredEnhancedConstructor(new ConstructorSignatureImpl(analyzer.getConstructor()));
JerseyConstructorInjectionPoint<T> constructorInjectionPoint = new JerseyConstructorInjectionPoint<T>(
constructor, bean, ((BasicInjectionTarget) injectionTarget).getBeanManager(), resolvers);
return constructorInjectionPoint;
}
return null;
}
private static <T> ConstructorInjectionPoint<T> createConstructorInjectionPoint(
ClassBinding<T> binding, Bean<T> bean, Collection<InjectionResolver> resolvers, BeanManager beanManager) {
final Class<T> bindingClass = binding.getImplementationType();
final AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(bindingClass);
final InjectionTarget<T> injectionTarget = beanManager.createInjectionTarget(annotatedType);
final CachedConstructorAnalyzer<T> analyzer =
new CachedConstructorAnalyzer<>(bindingClass, InjectionUtils.getInjectAnnotations(resolvers));
if (analyzer.hasCompatibleConstructor()) {
EnhancedAnnotatedConstructor<T> constructor = createEnhancedAnnotatedType((BasicInjectionTarget) injectionTarget)
.getDeclaredEnhancedConstructor(new ConstructorSignatureImpl(analyzer.getConstructor()));
JerseyConstructorInjectionPoint<T> constructorInjectionPoint = new JerseyConstructorInjectionPoint<T>(
constructor, bean, ((BasicInjectionTarget) injectionTarget).getBeanManager(), resolvers);
return constructorInjectionPoint;
}
return null;
}
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;
}
}
public static boolean isResourceClass(Class<?> clazz) {
if (isJaxrsResource(clazz)) {
return true;
}
for (Class<?> iface : clazz.getInterfaces()) {
if (isJaxrsResource(iface)) {
return true;
}
}
return false;
}
private static boolean isJaxrsResource(Class<?> clazz) {
if (clazz.isAnnotationPresent(Path.class)) {
return true;
}
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(Path.class)) {
return true;
}
for (Annotation annotation : method.getAnnotations()) {
if (annotation.annotationType().isAnnotationPresent(HttpMethod.class)) {
return true;
}
}
}
return false;
}
}