Allow SupplierClassBinding in ThreadScope in CDI
Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/BeanHelper.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/BeanHelper.java
index 81dc831..5de2aee 100644
--- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/BeanHelper.java
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/BeanHelper.java
@@ -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
@@ -129,13 +129,7 @@
* 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));
@@ -163,12 +157,18 @@
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);
+ }
}
/**
@@ -255,6 +255,14 @@
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;
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/InitializableSupplierThreadScopeBean.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/InitializableSupplierThreadScopeBean.java
index 5f144e0..31a4edf 100644
--- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/InitializableSupplierThreadScopeBean.java
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/InitializableSupplierThreadScopeBean.java
@@ -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
@@ -111,31 +111,4 @@
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();
- }
- }
}
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java
new file mode 100644
index 0000000..0326efe
--- /dev/null
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/SupplierThreadScopeClassBean.java
@@ -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 {
+ * @Override
+ * protected void configure() {
+ * bindFactory(MyFactoryInjectionSupplier.class)
+ * .to(MyBean.class)
+ * .in(PerThread.class);
+ * }
+ * }
+ * </pre>
+ * Inject example:
+ * <pre>
+ * @Path("/")
+ * public class MyResource {
+ * @Inject
+ * private MyBean myBean;
+ * }
+ * </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);
+ }
+}
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java
new file mode 100644
index 0000000..30d1896
--- /dev/null
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/ThreadScopeBeanInstance.java
@@ -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();
+ }
+}
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/managed/BinderRegisterExtension.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/managed/BinderRegisterExtension.java
index 9787cfe..b1d5ff7 100644
--- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/managed/BinderRegisterExtension.java
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/managed/BinderRegisterExtension.java
@@ -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
@@ -347,8 +347,10 @@
}
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
diff --git a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/CzechGreeting.java b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/CzechGreeting.java
index a60f546..16f82ef 100644
--- a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/CzechGreeting.java
+++ b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/CzechGreeting.java
@@ -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
@@ -26,9 +26,11 @@
static final String GREETING = "Ahoj";
+ private String greeting = GREETING + "#" + Thread.currentThread().getName();
+
@Override
public String getGreeting() {
- return GREETING + "#" + Thread.currentThread().getName();
+ return greeting;
}
@Override
diff --git a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/TestPreinitialization.java b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/TestPreinitialization.java
index a9096b0..5ec0f51 100644
--- a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/TestPreinitialization.java
+++ b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/TestPreinitialization.java
@@ -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
@@ -97,6 +97,13 @@
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
diff --git a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/ThreadScopeTest.java b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/ThreadScopeTest.java
index a87f8f9..19db6f1 100644
--- a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/ThreadScopeTest.java
+++ b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/managed/ThreadScopeTest.java
@@ -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
@@ -18,6 +18,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import javax.enterprise.context.RequestScoped;
@@ -181,7 +182,7 @@
}
@Test
- public void testThreadScopedInSingletonScope() {
+ public void testThreadScopedInSingletonScope() throws InterruptedException {
// InjectionManager injectionManager = BindingTestHelper.createInjectionManager();
// BindingTestHelper.bind(injectionManager, binder -> {
// binder.bindAsContract(SingletonObject.class)
@@ -202,6 +203,52 @@
assertNotNull(greeting2);
assertEquals(greeting1, greeting2);
+
+ final AtomicReference<String> greetingAtomicReference = new AtomicReference<>();
+ Runnable runnable = () ->
+ greetingAtomicReference.set(injectionManager.getInstance(SingletonObject.class).getGreeting().getGreeting());
+
+ Thread newThread = new Thread(runnable);
+ newThread.start();
+ newThread.join();
+
+ assertEquals(greeting1.getGreeting(), greeting2.getGreeting());
+ assertNotEquals(greeting1.getGreeting(), greetingAtomicReference.get());
+ }
+
+ @Test
+ public void testSupplierClassBindingThreadScopedInSingletonScope() throws InterruptedException {
+// InjectionManager injectionManager = BindingTestHelper.createInjectionManager();
+// BindingTestHelper.bind(injectionManager, binder -> {
+// binder.bindAsContract(SingletonObject.class)
+// .in(Singleton.class);
+//
+// binder.bindFactory(SupplierGreeting.class)
+// .to(Greeting.class)
+// .in(PerThread.class);
+// });
+
+ SingletonObject3 instance1 = injectionManager.getInstance(SingletonObject3.class);
+ Greeting3 greeting1 = instance1.getGreeting();
+ assertNotNull(greeting1);
+
+ // Precisely the same object
+ SingletonObject3 instance2 = injectionManager.getInstance(SingletonObject3.class);
+ Greeting3 greeting2 = instance2.getGreeting();
+ assertNotNull(greeting2);
+
+ assertEquals(greeting1, greeting2);
+
+ final AtomicReference<String> greetingAtomicReference = new AtomicReference<>();
+ Runnable runnable = () ->
+ greetingAtomicReference.set(injectionManager.getInstance(SingletonObject3.class).getGreeting().getGreeting());
+
+ Thread newThread = new Thread(runnable);
+ newThread.start();
+ newThread.join();
+
+ assertEquals(greeting1.getGreeting(), greeting2.getGreeting());
+ assertNotEquals(greeting1.getGreeting(), greetingAtomicReference.get());
}
@RequestScoped
@@ -227,6 +274,17 @@
}
@Singleton
+ public static class SingletonObject3 {
+
+ @Inject
+ Greeting3 greeting;
+
+ public Greeting3 getGreeting() {
+ return greeting;
+ }
+ }
+
+ @Singleton
public static class SingletonObject {
@Inject
@@ -260,6 +318,14 @@
}
@Vetoed
+ static class SupplierGreeting3 implements Supplier<Greeting3> {
+ @Override
+ public Greeting3 get() {
+ return new CzechGreeting3();
+ }
+ }
+
+ @Vetoed
static class SupplierGreeting2 implements Supplier<Greeting2> {
private final String greetingType;
@@ -291,13 +357,33 @@
}
@Vetoed
+ static class CzechGreeting3 implements Greeting3 {
+
+ static final String GREETING = "Ahoj";
+
+ private String greeting = GREETING + "#" + Thread.currentThread().getName();
+
+ @Override
+ public String getGreeting() {
+ return greeting;
+ }
+
+ @Override
+ public String toString() {
+ return "CzechGreeting";
+ }
+ }
+
+ @Vetoed
static class CzechGreeting2 implements Greeting2 {
static final String GREETING = "Ahoj";
+ private String greeting = GREETING + "#" + Thread.currentThread().getName();
+
@Override
public String getGreeting() {
- return GREETING + "#" + Thread.currentThread().getName();
+ return greeting;
}
@Override
@@ -374,9 +460,11 @@
static final String GREETING = "Ahoj";
+ private String greeting = GREETING + "#" + Thread.currentThread().getName();
+
@Override
public String getGreeting() {
- return GREETING + "#" + Thread.currentThread().getName();
+ return greeting;
}
@Override
@@ -386,6 +474,11 @@
}
@FunctionalInterface
+ static interface Greeting3 {
+ String getGreeting();
+ }
+
+ @FunctionalInterface
static interface Greeting2 {
String getGreeting();
}
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/BeanHelper.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/BeanHelper.java
index 1a40836..5a6e7a1 100644
--- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/BeanHelper.java
+++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/BeanHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -116,18 +116,11 @@
* @param <T> type of the instance which is registered.
*/
public static <T> void registerSupplier(SupplierInstanceBinding<T> binding, AfterBeanDiscovery abd, BeanManager beanManager) {
- BeanManagerImpl manager;
- if (beanManager instanceof BeanManagerProxy) {
- manager = ((BeanManagerProxy) beanManager).unwrap();
- } else {
- manager = (BeanManagerImpl) beanManager;
- }
-
/*
* CDI does not provide sufficient support for ThreadScoped Supplier
*/
if (binding.getScope() == PerThread.class) {
- abd.addBean(new SupplierThreadScopeBean(binding, manager));
+ abd.addBean(new SupplierThreadScopeBean(binding, beanManagerImpl(beanManager)));
} else {
abd.addBean(new SupplierInstanceBean<>(binding));
abd.addBean(new SupplierInstanceBeanBridge<>(binding));
@@ -155,8 +148,23 @@
InjectionTarget<Supplier<T>> jit = getJerseyInjectionTarget(supplierClass, injectionTarget, supplierBean, resolvers);
supplierBean.setInjectionTarget(jit);
- abd.addBean(supplierBean);
- abd.addBean(new SupplierBeanBridge(binding, beanManager));
+ /*
+ * 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,
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeBean.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeBean.java
index 67ccded..a989daf 100644
--- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeBean.java
+++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -17,20 +17,13 @@
package org.glassfish.jersey.inject.cdi.se.bean;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.WeakHashMap;
-import java.util.function.Supplier;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
-import javax.enterprise.inject.spi.Bean;
-import javax.enterprise.inject.spi.PassivationCapable;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;
-import org.jboss.weld.bean.StringBeanIdentifier;
import org.jboss.weld.bean.proxy.BeanInstance;
-import org.jboss.weld.bean.proxy.ContextBeanInstance;
import org.jboss.weld.bean.proxy.ProxyFactory;
import org.jboss.weld.manager.BeanManagerImpl;
@@ -107,31 +100,4 @@
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();
- }
- }
}
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java
new file mode 100644
index 0000000..28e69e7
--- /dev/null
+++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/SupplierThreadScopeClassBean.java
@@ -0,0 +1,111 @@
+/*
+ * 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.cdi.se.bean;
+
+import java.lang.annotation.Annotation;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+
+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;
+
+
+/**
+ * Creates an implementation of {@link jakarta.enterprise.inject.spi.Bean} interface using Jersey's {@link SupplierInstanceBinding}.
+ * Binding provides the information about the bean also called {@link jakarta.enterprise.inject.spi.BeanAttributes} information.
+ * The {@code Bean} does not use {@link org.glassfish.jersey.inject.cdi.se.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 {
+ * @Override
+ * protected void configure() {
+ * bindFactory(MyFactoryInjectionSupplier.class)
+ * .to(MyBean.class)
+ * .in(PerThread.class);
+ * }
+ * }
+ * </pre>
+ * Inject example:
+ * <pre>
+ * @Path("/")
+ * public class MyResource {
+ * @Inject
+ * private MyBean myBean;
+ * }
+ * </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(SupplierClassBinding binding, SupplierClassBean supplierClassBean, BeanManagerImpl beanManager) {
+ super(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);
+ }
+}
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java
new file mode 100644
index 0000000..3067386
--- /dev/null
+++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/ThreadScopeBeanInstance.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.jboss.weld.bean.StringBeanIdentifier;
+import org.jboss.weld.bean.proxy.ContextBeanInstance;
+
+import java.lang.reflect.Method;
+import java.util.WeakHashMap;
+import java.util.function.Supplier;
+
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.PassivationCapable;
+
+/**
+ * {@link org.glassfish.jersey.internal.inject.PerThread} scope bean instance used from
+ * {@link SupplierThreadScopeBean} 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();
+ }
+}
diff --git a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/CzechGreeting.java b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/CzechGreeting.java
index 0c7b9e8..a5326bb 100644
--- a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/CzechGreeting.java
+++ b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/CzechGreeting.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -26,9 +26,11 @@
static final String GREETING = "Ahoj";
+ private String greeting = GREETING + "#" + Thread.currentThread().getName();
+
@Override
public String getGreeting() {
- return GREETING + "#" + Thread.currentThread().getName();
+ return greeting;
}
@Override
diff --git a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/ThreadScopeTest.java b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/ThreadScopeTest.java
index 1c3ded4..1f88d53 100644
--- a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/ThreadScopeTest.java
+++ b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/ThreadScopeTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -17,6 +17,7 @@
package org.glassfish.jersey.inject.cdi.se;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
@@ -167,7 +168,7 @@
}
@Test
- public void testThreadScopedInSingletonScope() {
+ public void testThreadScopedInSingletonScope() throws InterruptedException {
InjectionManager injectionManager = BindingTestHelper.createInjectionManager();
BindingTestHelper.bind(injectionManager, binder -> {
binder.bindAsContract(SingletonObject.class)
@@ -188,6 +189,52 @@
assertNotNull(greeting2);
assertEquals(greeting1, greeting2);
+
+ final AtomicReference<String> greetingAtomicReference = new AtomicReference<>();
+ Runnable runnable = () ->
+ greetingAtomicReference.set(injectionManager.getInstance(SingletonObject.class).getGreeting().getGreeting());
+
+ Thread newThread = new Thread(runnable);
+ newThread.start();
+ newThread.join();
+
+ assertEquals(greeting1.getGreeting(), greeting2.getGreeting());
+ assertNotEquals(greeting1.getGreeting(), greetingAtomicReference.get());
+ }
+
+ @Test
+ public void testSupplierClassBindingThreadScopedInSingletonScope() throws InterruptedException {
+ InjectionManager injectionManager = BindingTestHelper.createInjectionManager();
+ BindingTestHelper.bind(injectionManager, binder -> {
+ binder.bindAsContract(SingletonObject.class)
+ .in(Singleton.class);
+
+ binder.bindFactory(SupplierGreeting.class)
+ .to(Greeting.class)
+ .in(PerThread.class);
+ });
+
+ SingletonObject instance1 = injectionManager.getInstance(SingletonObject.class);
+ Greeting greeting1 = instance1.getGreeting();
+ assertNotNull(greeting1);
+
+ // Precisely the same object
+ SingletonObject instance2 = injectionManager.getInstance(SingletonObject.class);
+ Greeting greeting2 = instance2.getGreeting();
+ assertNotNull(greeting2);
+
+ assertEquals(greeting1, greeting2);
+
+ final AtomicReference<String> greetingAtomicReference = new AtomicReference<>();
+ Runnable runnable = () ->
+ greetingAtomicReference.set(injectionManager.getInstance(SingletonObject.class).getGreeting().getGreeting());
+
+ Thread newThread = new Thread(runnable);
+ newThread.start();
+ newThread.join();
+
+ assertEquals(greeting1.getGreeting(), greeting2.getGreeting());
+ assertNotEquals(greeting1.getGreeting(), greetingAtomicReference.get());
}
@RequestScoped