diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 7b0baed226714..046d0d90f0bdc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -249,6 +249,7 @@ Collection generateSyntheticBean(BeanInfo bean) { implementGetKind(beanCreator, InjectableBean.Kind.SYNTHETIC); implementEquals(bean, beanCreator); implementHashCode(bean, beanCreator); + implementToString(beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -342,6 +343,7 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass) { implementIsSuppressed(bean, beanCreator); implementEquals(bean, beanCreator); implementHashCode(bean, beanCreator); + implementToString(beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -445,6 +447,7 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc implementIsSuppressed(bean, beanCreator); implementEquals(bean, beanCreator); implementHashCode(bean, beanCreator); + implementToString(beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -533,6 +536,7 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer implementIsSuppressed(bean, beanCreator); implementEquals(bean, beanCreator); implementHashCode(bean, beanCreator); + implementToString(beanCreator); beanCreator.close(); return classOutput.getResources(); @@ -1697,6 +1701,11 @@ protected void implementHashCode(BeanInfo bean, ClassCreator beanCreator) { hashCode.returnValue(constantHashCodeResult); } + protected void implementToString(ClassCreator beanCreator) { + MethodCreator toString = beanCreator.getMethodCreator("toString", String.class).setModifiers(ACC_PUBLIC); + toString.returnValue(toString.invokeStaticMethod(MethodDescriptors.BEANS_TO_STRING, toString.getThis())); + } + /** * * @param bean diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index b1f8dfad9edae..c0b52952f1476 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -272,6 +272,10 @@ public final class MethodDescriptors { ComponentsProvider.class, "unableToLoadRemovedBeanType", void.class, String.class, Throwable.class); + public static final MethodDescriptor BEANS_TO_STRING = MethodDescriptor.ofMethod(io.quarkus.arc.impl.Beans.class, + "toString", String.class, + InjectableBean.class); + private MethodDescriptors() { } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java index ca800d59a5f80..edb5bcc8ffd6b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java @@ -148,7 +148,9 @@ enum Kind { PRODUCER_METHOD, SYNTHETIC, INTERCEPTOR, - DECORATOR; + DECORATOR, + BUILTIN, + ; public static Kind from(String value) { for (Kind kind : values()) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index cce7e44a0ed7f..53a49439e23cc 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -115,10 +115,10 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory) { applicationContext = new ApplicationContext(); singletonContext = new SingletonContext(); requestContext = new RequestContext(this.currentContextFactory.create(RequestScoped.class)); - contexts = new HashMap<>(); - putContext(requestContext); - putContext(applicationContext); - putContext(singletonContext); + Map, List> contexts = new HashMap<>(); + putContext(requestContext, contexts); + putContext(applicationContext, contexts); + putContext(singletonContext, contexts); for (ComponentsProvider componentsProvider : ServiceLoader.load(ComponentsProvider.class)) { Components components = componentsProvider.getComponents(); @@ -143,11 +143,14 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory) { throw new IllegalStateException( "Failed to register a context - built-in singleton context is always active: " + context); } - putContext(context); + putContext(context, contexts); } transitiveInterceptorBindings.putAll(components.getTransitiveInterceptorBindings()); qualifierNonbindingMembers.putAll(components.getQualifierNonbindingMembers()); } + + this.contexts = Map.copyOf(contexts); + // register built-in beans addBuiltInBeans(beans); @@ -173,7 +176,7 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory) { this.qualifierNonbindingMembers = Map.copyOf(qualifierNonbindingMembers); } - private void putContext(InjectableContext context) { + private void putContext(InjectableContext context, Map, List> contexts) { Collection values = contexts.get(context.getScope()); if (values == null) { contexts.put(context.getScope(), Collections.singletonList(context)); @@ -181,7 +184,7 @@ private void putContext(InjectableContext context) { List multi = new ArrayList<>(values.size() + 1); multi.addAll(values); multi.add(context); - contexts.put(context.getScope(), Collections.unmodifiableList(multi)); + contexts.put(context.getScope(), List.copyOf(multi)); } } @@ -407,7 +410,6 @@ public synchronized void shutdown() { // Clear caches Reflections.clearCaches(); - contexts.clear(); resolved.clear(); running.set(false); InterceptedStaticMethods.clear(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Beans.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Beans.java new file mode 100644 index 0000000000000..5c9d64ce75b68 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Beans.java @@ -0,0 +1,21 @@ +package io.quarkus.arc.impl; + +import io.quarkus.arc.InjectableBean; + +public class Beans { + + private Beans() { + } + + public static String toString(InjectableBean bean) { + return new StringBuilder() + .append(bean.getKind()) + .append(" bean [class=") + .append(bean.getBeanClass().getName()) + .append(", id=") + .append(bean.getIdentifier()) + .append("]") + .toString(); + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BuiltInBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BuiltInBean.java index 1d0ee1f33bd41..fa68edf4560dd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BuiltInBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BuiltInBean.java @@ -18,4 +18,15 @@ public String getIdentifier() { public T create(CreationalContext creationalContext) { return get(creationalContext); } + + @Override + public Kind getKind() { + return Kind.BUILTIN; + } + + @Override + public String toString() { + return Beans.toString(this); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java index 9469bf21f4e1a..3b0a8109d540e 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ClientProxies.java @@ -5,6 +5,7 @@ import io.quarkus.arc.InjectableContext; import java.util.List; import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.context.RequestScoped; import javax.enterprise.context.spi.Contextual; public final class ClientProxies { @@ -43,7 +44,13 @@ public static T getDelegate(InjectableBean bean) { } } if (result == null) { - throw new ContextNotActiveException(); + String msg = String.format( + "%s context was not active when trying to obtain a bean instance for a client proxy of %s", + bean.getScope().getSimpleName(), bean); + if (bean.getScope().equals(RequestScoped.class)) { + msg += "\n\t- you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding"; + } + throw new ContextNotActiveException(msg); } return result; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java index 71c52cd857fa0..c7b3e07684739 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java @@ -81,7 +81,7 @@ public T get(Contextual contextual, CreationalContext creationalContex T result = getIfActive(contextual, CreationalContextImpl.unwrap(Objects.requireNonNull(creationalContext, "CreationalContext must not be null"))); if (result == null) { - throw new ContextNotActiveException(); + throw notActive(); } return result; } @@ -96,7 +96,7 @@ public T get(Contextual contextual) { } RequestContextState state = currentContext.get(); if (state == null) { - throw new ContextNotActiveException(); + throw notActive(); } ContextInstanceHandle instance = (ContextInstanceHandle) state.map.get(contextual); return instance == null ? null : instance.get(); @@ -112,7 +112,7 @@ public void destroy(Contextual contextual) { RequestContextState state = currentContext.get(); if (state == null) { // Context is not active - throw new ContextNotActiveException(); + throw notActive(); } ContextInstanceHandle instance = state.map.remove(contextual); if (instance != null) { @@ -140,7 +140,7 @@ public ContextState getState() { RequestContextState state = currentContext.get(); if (state == null) { // Thread local not set - context is not active! - throw new ContextNotActiveException(); + throw notActive(); } return state; } @@ -206,6 +206,11 @@ private void fireIfNotEmpty(LazyValue> value) { } } + private ContextNotActiveException notActive() { + String msg = "Request context is not active - you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding"; + return new ContextNotActiveException(msg); + } + private static Notifier createInitializedNotifier() { return EventImpl.createNotifier(Object.class, Object.class, new HashSet<>(Arrays.asList(Initialized.Literal.REQUEST, Any.Literal.INSTANCE)), diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/contextnotactive/ClientProxyContextNotActiveTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/contextnotactive/ClientProxyContextNotActiveTest.java new file mode 100644 index 0000000000000..cac1e4ad3c145 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/contextnotactive/ClientProxyContextNotActiveTest.java @@ -0,0 +1,34 @@ +package io.quarkus.arc.test.clientproxy.contextnotactive; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.context.RequestScoped; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ClientProxyContextNotActiveTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(RequestFoo.class); + + @Test + public void testToStringIsDelegated() { + RequestFoo foo = Arc.container().instance(RequestFoo.class).get(); + assertThatExceptionOfType(ContextNotActiveException.class).isThrownBy(() -> foo.ping()) + .withMessageContaining( + "RequestScoped context was not active when trying to obtain a bean instance for a client proxy of CLASS bean [class=io.quarkus.arc.test.clientproxy.contextnotactive.ClientProxyContextNotActiveTest$RequestFoo, id=3e5a77b35b0824bc957993f6db95a37e766e929e]") + .withMessageContaining( + "you can activate the request context for a specific method using the @ActivateRequestContext interceptor binding"); + } + + @RequestScoped + static class RequestFoo { + + void ping() { + } + + } +}