Skip to content

Commit

Permalink
ArC: add InterceptionProxySubclass
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Dec 6, 2024
1 parent b9de708 commit a62f036
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 7 deletions.
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) {
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());
}
}
}

0 comments on commit a62f036

Please sign in to comment.