Repository: quarkusio/quarkus
Issue: Instance.destroy() - support client proxies 

Currently, we only support `@Dependent` contextual instances.

Changed files:
independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ClientProxy.java
independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/constructor/ClientProxyConstructorGuardTest.java
extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ClientProxyUnwrapper.java
independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java

diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ClientProxyUnwrapper.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ClientProxyUnwrapper.java
index 83adb8a6732..0a9e3e44d55 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ClientProxyUnwrapper.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ClientProxyUnwrapper.java
@@ -8,7 +8,7 @@ public class ClientProxyUnwrapper implements Function<Object, Object> {
     @Override
     public Object apply(Object o) {
         if (o instanceof ClientProxy) {
-            return ((ClientProxy) o).getContextualInstance();
+            return ((ClientProxy) o).arc_contextualInstance();
         }
         return o;
     }
diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
index 65e681d7673..fb25eacbf2e 100644
--- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
+++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java
@@ -39,7 +39,8 @@ public class ClientProxyGenerator extends AbstractGenerator {
     static final String CLIENT_PROXY_SUFFIX = "_ClientProxy";
 
     static final String DELEGATE_METHOD_NAME = "arc$delegate";
-    static final String GET_CONTEXTUAL_INSTANCE_METHOD_NAME = "getContextualInstance";
+    static final String GET_CONTEXTUAL_INSTANCE_METHOD_NAME = "arc_contextualInstance";
+    static final String GET_BEAN = "arc_bean";
 
     private final Predicate<DotName> applicationClassPredicate;
 
@@ -86,6 +87,7 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName, ReflectionReg
         createConstructor(clientProxy, beanClassName, superClass, beanField.getFieldDescriptor());
         implementDelegate(clientProxy, providerTypeName, beanField.getFieldDescriptor());
         implementGetContextualInstance(clientProxy, providerTypeName);
+        implementGetBean(clientProxy, beanField.getFieldDescriptor());
 
         for (MethodInfo method : getDelegatingMethods(bean)) {
 
@@ -209,6 +211,12 @@ void implementGetContextualInstance(ClassCreator clientProxy, String providerTyp
                         creator.getThis()));
     }
 
+    void implementGetBean(ClassCreator clientProxy, FieldDescriptor beanField) {
+        MethodCreator creator = clientProxy.getMethodCreator(GET_BEAN, InjectableBean.class)
+                .setModifiers(Modifier.PUBLIC);
+        creator.returnValue(creator.readInstanceField(beanField, creator.getThis()));
+    }
+
     Collection<MethodInfo> getDelegatingMethods(BeanInfo bean) {
         Map<Methods.MethodKey, MethodInfo> methods = new HashMap<>();
 
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 9dba1635d7b..cda38781588 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
@@ -7,6 +7,8 @@
  */
 public interface ClientProxy {
 
-    Object getContextualInstance();
+    Object arc_contextualInstance();
+
+    InjectableBean<?> arc_bean();
 
 }
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java
index 728e4e4eddf..b67bb0da3f8 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java
@@ -9,7 +9,9 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Objects;
 import java.util.Set;
+import javax.enterprise.context.ContextNotActiveException;
 import javax.enterprise.inject.AmbiguousResolutionException;
 import javax.enterprise.inject.Instance;
 import javax.enterprise.inject.UnsatisfiedResolutionException;
@@ -114,11 +116,18 @@ public boolean isAmbiguous() {
 
     @Override
     public void destroy(T instance) {
+        Objects.requireNonNull(instance);
         if (instance instanceof ClientProxy) {
-            throw new UnsupportedOperationException();
+            ClientProxy proxy = (ClientProxy) instance;
+            InjectableContext context = Arc.container().getActiveContext(proxy.arc_bean().getScope());
+            if (context == null) {
+                throw new ContextNotActiveException("No active context found for: " + proxy.arc_bean().getScope());
+            }
+            context.destroy(proxy.arc_bean());
+        } else {
+            // Try to destroy a dependent instance
+            creationalContext.destroyDependentInstance(instance);
         }
-        // Try to destroy a dependent instance
-        creationalContext.destroyDependentInstance(instance);
     }
 
     void destroy() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
index 1f0968e79f6..ee20e2d2c6a 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
@@ -21,7 +21,7 @@ public class ClientProxyGetContextualInstanceTest {
     public void testProducer() throws IOException {
         Moo moo = Arc.container().instance(Moo.class).get();
         assertTrue(moo instanceof ClientProxy);
-        assertEquals(10, ((Moo) ((ClientProxy) moo).getContextualInstance()).val);
+        assertEquals(10, ((Moo) ((ClientProxy) moo).arc_contextualInstance()).val);
     }
 
     @ApplicationScoped
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/constructor/ClientProxyConstructorGuardTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/constructor/ClientProxyConstructorGuardTest.java
index b4fd1462388..fc56ff783bb 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/constructor/ClientProxyConstructorGuardTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/constructor/ClientProxyConstructorGuardTest.java
@@ -20,7 +20,7 @@ public class ClientProxyConstructorGuardTest {
     public void testProducer() throws IOException {
         Moo moo = Arc.container().instance(Moo.class).get();
         assertTrue(moo instanceof ClientProxy);
-        assertEquals(10, ((Moo) ((ClientProxy) moo).getContextualInstance()).val);
+        assertEquals(10, ((Moo) ((ClientProxy) moo).arc_contextualInstance()).val);
     }
 
     @ApplicationScoped
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
index c317171f983..5135c32daba 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
@@ -1,12 +1,16 @@
 package io.quarkus.arc.test.instance.destroy;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.test.ArcTestContainer;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
+import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Instance;
 import javax.inject.Inject;
@@ -17,13 +21,19 @@
 public class InstanceDestroyTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(Alpha.class, Washcloth.class);
+    public ArcTestContainer container = new ArcTestContainer(Alpha.class, Washcloth.class, Knight.class);
 
     @Test
     public void testDestroy() {
         assertFalse(Washcloth.DESTROYED.get());
         Arc.container().instance(Alpha.class).get().doSomething();
         assertTrue(Washcloth.DESTROYED.get());
+        // App scoped beans
+        Knight knight1 = Arc.container().instance(Knight.class).get();
+        String id1 = knight1.getId();
+        Arc.container().beanManager().createInstance().destroy(knight1);
+        assertTrue(Knight.DESTROYED.get());
+        assertNotEquals(id1, Arc.container().instance(Knight.class).get().getId());
     }
 
     @Singleton
@@ -55,4 +65,27 @@ void destroy() {
 
     }
 
+    @ApplicationScoped
+    static class Knight {
+
+        static final AtomicBoolean DESTROYED = new AtomicBoolean(false);
+
+        String id;
+
+        String getId() {
+            return id;
+        }
+
+        @PostConstruct
+        void init() {
+            this.id = UUID.randomUUID().toString();
+        }
+
+        @PreDestroy
+        void destroy() {
+            DESTROYED.set(true);
+        }
+
+    }
+
 }