Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArC: add InterceptionProxySubclass #44947

Merged
merged 1 commit into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T>
* @param obj
* @param <T> 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> T unwrap(T obj) {
if (obj instanceof ClientProxy) {
return (T) ((ClientProxy) obj).arc_contextualInstance();
if (obj instanceof ClientProxy proxy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I really like pattern matching for instanceof!

return (T) proxy.arc_contextualInstance();
}
return obj;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This method should only be used with caution. If you unwrap an interception proxy,
* then certain key functionality will not work as expected.
*
* @param <T> 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> T unwrap(T obj) {
if (obj instanceof InterceptionProxySubclass proxy) {
return (T) proxy.arc_delegate();
}
return obj;
}
}
Original file line number Diff line number Diff line change
@@ -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<MyNonbean> proxy) {
return proxy.create(new MyNonbean());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MyNonbean> proxy) {
return proxy.create(new MyNonbean());
}
}
}
Loading