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