diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptionProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptionProxyGenerator.java index 49d790bb94d31..9ff06914aca7a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptionProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptionProxyGenerator.java @@ -24,7 +24,7 @@ import io.quarkus.arc.InjectableReferenceProvider; import io.quarkus.arc.InterceptionProxy; -import io.quarkus.arc.Subclass; +import io.quarkus.arc.InterceptionProxySubclass; import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.gizmo.BytecodeCreator; @@ -156,8 +156,8 @@ private void createInterceptionSubclass(ClassOutput classOutput, InterceptionPro String superClass = isInterface ? Object.class.getName() : pseudoBeanClassName; String[] interfaces = isInterface - ? new String[] { pseudoBeanClassName, Subclass.class.getName() } - : new String[] { Subclass.class.getName() }; + ? new String[] { pseudoBeanClassName, InterceptionProxySubclass.class.getName() } + : new String[] { InterceptionProxySubclass.class.getName() }; try (ClassCreator clazz = ClassCreator.builder() .classOutput(classOutput) @@ -352,6 +352,9 @@ private void createInterceptionSubclass(ClassOutput classOutput, InterceptionPro ctor.writeInstanceField(constructedField.getFieldDescriptor(), ctor.getThis(), ctor.load(true)); ctor.returnVoid(); + + MethodCreator getDelegate = clazz.getMethodCreator("arc_delegate", Object.class); + getDelegate.returnValue(getDelegate.readInstanceField(delegate.getFieldDescriptor(), getDelegate.getThis())); } } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ClientProxy.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ClientProxy.java index 8af0bdf78951d..be10c658e31c4 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ClientProxy.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ClientProxy.java @@ -42,14 +42,14 @@ public interface ClientProxy { * This method should only be used with caution. If you unwrap a client proxy then certain key functionality will not work * as expected. * - * @param - * @param obj + * @param the type of the object to unwrap + * @param obj the object to unwrap * @return the contextual instance if the object represents a client proxy, the object otherwise */ @SuppressWarnings("unchecked") static T unwrap(T obj) { - if (obj instanceof ClientProxy) { - return (T) ((ClientProxy) obj).arc_contextualInstance(); + if (obj instanceof ClientProxy proxy) { + return (T) proxy.arc_contextualInstance(); } return obj; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptionProxySubclass.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptionProxySubclass.java new file mode 100644 index 0000000000000..6b4afb3cf432f --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptionProxySubclass.java @@ -0,0 +1,33 @@ +package io.quarkus.arc; + +/** + * Represents an interception proxy. Typically, interception is performed by creating a subclass + * of the original class and arranging bean instantiation such that the contextual instance + * is in fact an instance of the subclass, but that isn't always possible. In case of + * {@link InterceptionProxy}, interception is performed by a proxy that delegates to the actual + * contextual instance. Such proxy implements this interface. + */ +public interface InterceptionProxySubclass extends Subclass { + /** + * @return the contextual instance + */ + Object arc_delegate(); + + /** + * Attempts to unwrap the object if it represents an interception proxy. + *

+ * This method should only be used with caution. If you unwrap an interception proxy, + * then certain key functionality will not work as expected. + * + * @param the type of the object to unwrap + * @param obj the object to unwrap + * @return the contextual instance if the object represents an interception proxy, the object otherwise + */ + @SuppressWarnings("unchecked") + static T unwrap(T obj) { + if (obj instanceof InterceptionProxySubclass proxy) { + return (T) proxy.arc_delegate(); + } + return obj; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassNormalScopedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassNormalScopedTest.java new file mode 100644 index 0000000000000..e636ea4d69908 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassNormalScopedTest.java @@ -0,0 +1,83 @@ +package io.quarkus.arc.test.interceptors.producer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InterceptionProxy; +import io.quarkus.arc.InterceptionProxySubclass; +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptionProxySubclassNormalScopedTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBinding.class, MyInterceptor.class, MyProducer.class); + + @Test + public void test() { + MyNonbean nonbean = Arc.container().instance(MyNonbean.class).get(); + assertEquals("intercepted: hello", nonbean.hello()); + + assertInstanceOf(ClientProxy.class, nonbean); + assertNotNull(ClientProxy.unwrap(nonbean)); + assertNotSame(nonbean, ClientProxy.unwrap(nonbean)); + + MyNonbean unwrapped = ClientProxy.unwrap(nonbean); + + assertInstanceOf(InterceptionProxySubclass.class, unwrapped); + assertNotNull(InterceptionProxySubclass.unwrap(unwrapped)); + assertNotSame(unwrapped, InterceptionProxySubclass.unwrap(unwrapped)); + assertNotSame(nonbean, InterceptionProxySubclass.unwrap(unwrapped)); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR }) + @InterceptorBinding + @interface MyBinding { + } + + @MyBinding + @Priority(1) + @Interceptor + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } + + static class MyNonbean { + @MyBinding + String hello() { + return "hello"; + } + } + + @Dependent + static class MyProducer { + @Produces + @ApplicationScoped + MyNonbean produce(InterceptionProxy proxy) { + return proxy.create(new MyNonbean()); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassPseudoScopedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassPseudoScopedTest.java new file mode 100644 index 0000000000000..8babcdb5ca801 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/producer/InterceptionProxySubclassPseudoScopedTest.java @@ -0,0 +1,75 @@ +package io.quarkus.arc.test.interceptors.producer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InterceptionProxy; +import io.quarkus.arc.InterceptionProxySubclass; +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptionProxySubclassPseudoScopedTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBinding.class, MyInterceptor.class, MyProducer.class); + + @Test + public void test() { + MyNonbean nonbean = Arc.container().instance(MyNonbean.class).get(); + assertEquals("intercepted: hello", nonbean.hello()); + + assertInstanceOf(InterceptionProxySubclass.class, nonbean); + assertNotNull(InterceptionProxySubclass.unwrap(nonbean)); + assertNotSame(nonbean, InterceptionProxySubclass.unwrap(nonbean)); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR }) + @InterceptorBinding + @interface MyBinding { + } + + @MyBinding + @Priority(1) + @Interceptor + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } + + static class MyNonbean { + @MyBinding + String hello() { + return "hello"; + } + } + + @Dependent + static class MyProducer { + @Produces + @Singleton + MyNonbean produce(InterceptionProxy proxy) { + return proxy.create(new MyNonbean()); + } + } +}