Skip to content

Commit

Permalink
Allow SupplierClassBinding in ThreadScope in CDI
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <[email protected]>
  • Loading branch information
jansupol committed Aug 18, 2024
1 parent e254ae8 commit b752b3c
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand Down Expand Up @@ -129,13 +129,7 @@ public static <T> void registerSupplier(RuntimeType runtimeType, InitializableSu
* 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));
abd.addBean(new InitializableSupplierThreadScopeBean(runtimeType, binding, beanManagerImpl(beanManager)));
} else {
abd.addBean(new InitializableSupplierInstanceBean<>(runtimeType, binding));
abd.addBean(new InitializableSupplierInstanceBeanBridge<>(runtimeType, binding));
Expand Down Expand Up @@ -163,12 +157,18 @@ public static <T> BindingBeanPair registerSupplier(RuntimeType runtimeType, Supp
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);
/*
* CDI does not provide sufficient support for ThreadScoped Supplier
*/
if (binding.getScope() == PerThread.class) {
abd.addBean(new SupplierThreadScopeClassBean(runtimeType, binding, supplierBean, beanManagerImpl(beanManager)));
return null;
} else {
final SupplierBeanBridge supplierBeanBridge = new SupplierBeanBridge(runtimeType, binding, beanManager);
abd.addBean(supplierBean);
abd.addBean(supplierBeanBridge);
return new BindingBeanPair(binding, supplierBean, supplierBeanBridge);
}
}

/**
Expand Down Expand Up @@ -255,6 +255,14 @@ private static <T> ConstructorInjectionPoint<T> createConstructorInjectionPoint(
return null;
}

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand Down Expand Up @@ -111,31 +111,4 @@ private <T> T createClientProxy(BeanInstance beanInstance, String contextId) {
ProxyFactory<T> factory = new ProxyFactory<>(contextId, getBeanClass(), getTypes(), this);
return factory.create(beanInstance);
}

private static class ThreadScopeBeanInstance<T> extends ContextBeanInstance<T> {

private final WeakHashMap<Thread, Object> instances = new WeakHashMap<>();

private final Supplier<T> supplier;

/**
* Creates a new invocation handler with supplier which provides a current injected value in proper scope.
*
* @param supplier provider of the value.
*/
private ThreadScopeBeanInstance(Supplier<T> supplier, Bean<T> bean, String contextId) {
super(bean, new StringBeanIdentifier(((PassivationCapable) bean).getId()), contextId);
this.supplier = supplier;
}

@Override
public Object invoke(Object obj, Method method, Object... arguments) throws Throwable {
Object instance = instances.computeIfAbsent(Thread.currentThread(), thread -> supplier.get());
return super.invoke(instance, method, arguments);
}

public void dispose() {
this.instances.clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 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.weld.internal.bean;

import org.glassfish.jersey.internal.inject.SupplierClassBinding;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.jboss.weld.bean.proxy.BeanInstance;
import org.jboss.weld.bean.proxy.ProxyFactory;
import org.jboss.weld.manager.BeanManagerImpl;

import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.ws.rs.RuntimeType;
import java.lang.annotation.Annotation;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;


/**
* Creates an implementation of {@link javax.enterprise.inject.spi.Bean} interface using Jersey's {@link SupplierInstanceBinding}.
* Binding provides the information about the bean also called {@link javax.enterprise.inject.spi.BeanAttributes} information.
* The {@code Bean} does not use {@link org.glassfish.jersey.inject.weld.internal.injector.JerseyInjectionTarget} because serves already
* created proxy, therefore the create operation just return provided instance without any other contextual operation
* (produce, inject, destroy).
* <p>
* This bean is special and is used only for service registered as a {@link org.glassfish.jersey.internal.inject.PerThread} and
* works through the proxy which serves the correct instance per the given thread.
* <p>
* Register example:
* <pre>
* AbstractBinder {
* &#64;Override
* protected void configure() {
* bindFactory(MyFactoryInjectionSupplier.class)
* .to(MyBean.class)
* .in(PerThread.class);
* }
* }
* </pre>
* Inject example:
* <pre>
* &#64;Path("/")
* public class MyResource {
* &#64;Inject
* private MyBean myBean&#59;
* }
* </pre>
*/
class SupplierThreadScopeClassBean extends JerseyBean<Object> {
private final LazyValue<ThreadScopeBeanInstance<Object>> beanInstance;
private final SupplierClassBinding binding;
private final LazyValue<Object> proxy;
private final AtomicReference<CreationalContext> creationalContextAtomicReference = new AtomicReference<>();

SupplierThreadScopeClassBean(RuntimeType runtimeType,
SupplierClassBinding binding,
SupplierClassBean supplierClassBean,
BeanManagerImpl beanManager) {
super(runtimeType, binding);
this.binding = binding;
this.beanInstance = Values.lazy((Value<ThreadScopeBeanInstance<Object>>) () -> {
Supplier supplierInstance = supplierClassBean.create(creationalContextAtomicReference.get());
ThreadScopeBeanInstance scopeBeanInstance =
new ThreadScopeBeanInstance(supplierInstance, this, beanManager.getContextId());
return scopeBeanInstance;
});
this.proxy = Values.lazy((Value<Object>) () -> createClientProxy(beanInstance.get(), beanManager.getContextId()));
}

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

@Override
public Object create(CreationalContext<Object> ctx) {
creationalContextAtomicReference.set(ctx);
return proxy.get();
}

@Override
public void destroy(Object instance, CreationalContext<Object> creationalContext) {
if (beanInstance.isInitialized()) {
this.beanInstance.get().dispose();
}
}

@Override
public Class<?> getBeanClass() {
return (Class<?>) this.binding.getContracts().iterator().next();
}

private <T> T createClientProxy(BeanInstance beanInstance, String contextId) {
ProxyFactory<T> factory = new ProxyFactory<>(contextId, getBeanClass(), getTypes(), this);
return factory.create(beanInstance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2021, 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.weld.internal.bean;

import org.jboss.weld.bean.StringBeanIdentifier;
import org.jboss.weld.bean.proxy.ContextBeanInstance;

import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.PassivationCapable;
import java.lang.reflect.Method;
import java.util.WeakHashMap;
import java.util.function.Supplier;

/**
* {@link org.glassfish.jersey.internal.inject.PerThread} scope bean instance used from
* {@link InitializableSupplierThreadScopeBean} and {@link SupplierThreadScopeClassBean}.
*
* @param <T> Typed of the bean supplied by a {@code Supplier}.
*/
class ThreadScopeBeanInstance<T> extends ContextBeanInstance<T> {

private final WeakHashMap<Thread, Object> instances = new WeakHashMap<>();

private final Supplier<T> supplier;

/**
* Creates a new invocation handler with supplier which provides a current injected value in proper scope.
*
* @param supplier provider of the value.
*/
ThreadScopeBeanInstance(Supplier<T> supplier, Bean<T> bean, String contextId) {
super(bean, new StringBeanIdentifier(((PassivationCapable) bean).getId()), contextId);
this.supplier = supplier;
}

@Override
public Object invoke(Object obj, Method method, Object... arguments) throws Throwable {
Object instance = instances.computeIfAbsent(Thread.currentThread(), thread -> supplier.get());
return super.invoke(instance, method, arguments);
}

public void dispose() {
this.instances.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand Down Expand Up @@ -347,8 +347,10 @@ private void registerBeans(RuntimeType runtimeType, CachingBinder binder, AfterB
}
BindingBeanPair pair = BeanHelper.registerSupplier(
runtimeType, (SupplierClassBinding<?>) binding, abd, injectionResolvers, beanManager);
for (Type contract : ((SupplierClassBinding<?>) binding).getContracts()) {
supplierClassBindings.add(contract, pair);
if (pair != null) {
for (Type contract : ((SupplierClassBinding<?>) binding).getContracts()) {
supplierClassBindings.add(contract, pair);
}
}
} else if (InitializableInstanceBinding.class.isAssignableFrom(binding.getClass())) {
if (RuntimeType.SERVER == runtimeType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand All @@ -26,9 +26,11 @@ public class CzechGreeting implements Greeting, Printable {

static final String GREETING = "Ahoj";

private String greeting = GREETING + "#" + Thread.currentThread().getName();

@Override
public String getGreeting() {
return GREETING + "#" + Thread.currentThread().getName();
return greeting;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand Down Expand Up @@ -97,6 +97,13 @@ public void register(RuntimeType runtimeType, AbstractBinder binder) {
binder.bindFactory(new ThreadScopeTest.SupplierGreeting2(ThreadScopeTest.EnglishGreeting2.GREETING))
.to(ThreadScopeTest.EnglishGreeting2.class)
.in(PerThread.class);

//testSupplierClassBindingThreadScopedInSingletonScope
binder.bindAsContract(ThreadScopeTest.SingletonObject3.class)
.in(Singleton.class);
binder.bindFactory(ThreadScopeTest.SupplierGreeting3.class)
.to(ThreadScopeTest.Greeting3.class)
.in(PerThread.class);
}

//ClientInstanceInjectionTest
Expand Down
Loading

0 comments on commit b752b3c

Please sign in to comment.