Repository: quarkusio/quarkus
Issue: Should the CamelRuntimeProducer be marked as unremovable?

A user reported having this in his logs:
```
2019-03-25 12:20:39,124 WARN  [io.qua.arc.run.ArcDeploymentTemplate] (main) Bean matching class io.quarkus.camel.core.runtime.CamelRuntimeProducer was marked as unused and removed during build.
Extensions can eliminate false positives using:
    - a custom UnremovableBeanBuildItem
    - AdditionalBeanBuildItem(false, beanClazz)
```

I think the `CamelRuntimeProducer` bean should probably be marked as unremovable.

Changed files:
core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java
extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java
extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java
extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java
extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java
extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java
extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java
extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java
index 381fd434841..4664f9e3add 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java
@@ -255,7 +255,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
                         return "Runtime proxy of " + returnType + " with id " + key;
                     }
                     throw new RuntimeException(
-                            "You cannot invoke directly on an object returned from the bytecode recorded, you can only pass is back into the recorder as a parameter");
+                            "You cannot invoke " + method.getName()
+                                    + "() directly on an object returned from the bytecode recorder, you can only pass it back into the recorder as a parameter");
                 }
             });
             ProxyInstance instance = new ProxyInstance(proxyInstance, key);
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java
index 799a005f611..b632f4fe90a 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java
@@ -10,6 +10,8 @@
 
 import org.jboss.builder.item.MultiBuildItem;
 
+import io.quarkus.runtime.RuntimeValue;
+
 /**
  * Represents a bean that can be easily produced through a template (or other runtime Supplier implementation)
  */
@@ -18,14 +20,22 @@ public final class RuntimeBeanBuildItem extends MultiBuildItem {
     final String scope;
     final String type;
     final Supplier<Object> supplier;
+    final RuntimeValue<?> runtimeValue;
     final NavigableMap<String, NavigableMap<String, Object>> qualifiers;
+    final boolean removable;
 
     RuntimeBeanBuildItem(String scope, String type, Supplier<Object> supplier,
-            NavigableMap<String, NavigableMap<String, Object>> qualifiers) {
+            NavigableMap<String, NavigableMap<String, Object>> qualifiers, boolean removable,
+            RuntimeValue<?> runtimeValue) {
+        if (supplier != null && runtimeValue != null) {
+            throw new IllegalArgumentException("It is not possible to specify both - a supplier and a runtime value");
+        }
         this.scope = scope;
         this.type = type;
         this.supplier = supplier;
         this.qualifiers = qualifiers;
+        this.removable = removable;
+        this.runtimeValue = runtimeValue;
     }
 
     public String getScope() {
@@ -40,26 +50,38 @@ public Supplier<Object> getSupplier() {
         return supplier;
     }
 
+    public RuntimeValue<?> getRuntimeValue() {
+        return runtimeValue;
+    }
+
+    public boolean isRemovable() {
+        return removable;
+    }
+
     public NavigableMap<String, NavigableMap<String, Object>> getQualifiers() {
         return qualifiers;
     }
 
-    public static Builder builder(String type, Supplier<Object> supplier) {
+    public static Builder builder(Class<?> type) {
+        return builder(type.getName());
+    }
+
+    public static Builder builder(String type) {
         Objects.requireNonNull(type);
-        Objects.requireNonNull(supplier);
-        return new Builder(type, supplier);
+        return new Builder(type);
     }
 
     public static class Builder {
 
         String scope = Dependent.class.getName();
+        boolean removable = true;
         final String type;
-        final Supplier<Object> supplier;
+        Supplier<Object> supplier;
+        RuntimeValue<?> value;
         final NavigableMap<String, NavigableMap<String, Object>> qualifiers = new TreeMap<>();
 
-        public Builder(String type, Supplier<Object> supplier) {
+        public Builder(String type) {
             this.type = type;
-            this.supplier = supplier;
         }
 
         public Builder setScope(String scope) {
@@ -90,8 +112,23 @@ public Builder addQualifier(Class<? extends Annotation> type, NavigableMap<Strin
             return addQualifier(type.getName(), values);
         }
 
+        public Builder setRemovable(boolean removable) {
+            this.removable = removable;
+            return this;
+        }
+
+        public Builder setSupplier(Supplier<Object> supplier) {
+            this.supplier = Objects.requireNonNull(supplier);
+            return this;
+        }
+
+        public Builder setRuntimeValue(RuntimeValue<?> runtimeValue) {
+            this.value = Objects.requireNonNull(runtimeValue);
+            return this;
+        }
+
         public RuntimeBeanBuildItem build() {
-            return new RuntimeBeanBuildItem(scope, type, supplier, qualifiers);
+            return new RuntimeBeanBuildItem(scope, type, supplier, qualifiers, removable, value);
         }
     }
 }
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java
index 867ea5f4e01..2e097f75889 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java
@@ -4,11 +4,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Produces;
 
+import io.quarkus.arc.processor.BeanInfo;
 import io.quarkus.arc.runtime.ArcDeploymentTemplate;
 import io.quarkus.deployment.annotations.BuildProducer;
 import io.quarkus.deployment.annotations.BuildStep;
@@ -29,7 +31,8 @@ public class RuntimeBeanProcessor {
     @Record(ExecutionTime.STATIC_INIT)
     void build(List<RuntimeBeanBuildItem> beans,
             BuildProducer<GeneratedBeanBuildItem> generatedBean,
-            ArcDeploymentTemplate template) {
+            ArcDeploymentTemplate template,
+            BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
         String beanName = "io.quarkus.arc.runtimebean.RuntimeBeanProducers";
 
         ClassCreator c = new ClassCreator(new ClassOutput() {
@@ -41,22 +44,35 @@ public void write(String name, byte[] data) {
 
         c.addAnnotation(ApplicationScoped.class);
         Map<String, Supplier<Object>> map = new HashMap<>();
-        for (RuntimeBeanBuildItem b : beans) {
+        for (RuntimeBeanBuildItem bean : beans) {
             //deterministic name
             //as we know the maps are sorted this will result in the same hash for the same bean
-            String name = b.type.replace(".", "_") + "_" + HashUtil.sha1(b.qualifiers.toString());
-            map.put(name, b.supplier);
+            String name = bean.type.replace(".", "_") + "_" + HashUtil.sha1(bean.qualifiers.toString());
+            if (bean.runtimeValue != null) {
+                map.put(name, template.createSupplier(bean.runtimeValue));
+            } else {
+                map.put(name, bean.supplier);
+            }
 
-            MethodCreator producer = c.getMethodCreator("produce_" + name, b.type);
+            MethodCreator producer = c.getMethodCreator("produce_" + name, bean.type);
             producer.addAnnotation(Produces.class);
-            producer.addAnnotation(b.scope);
-            for (Map.Entry<String, NavigableMap<String, Object>> i : b.qualifiers.entrySet()) {
-                AnnotationCreator builder = producer.addAnnotation(i.getKey());
-                for (Map.Entry<String, Object> j : i.getValue().entrySet()) {
-                    builder.addValue(i.getKey(), i.getValue());
+            producer.addAnnotation(bean.scope);
+            for (Map.Entry<String, NavigableMap<String, Object>> qualifierEntry : bean.qualifiers.entrySet()) {
+                AnnotationCreator builder = producer.addAnnotation(qualifierEntry.getKey());
+                for (Map.Entry<String, Object> valueEntry : qualifierEntry.getValue().entrySet()) {
+                    builder.addValue(valueEntry.getKey(), valueEntry.getValue());
                 }
             }
 
+            if (!bean.removable) {
+                unremovableBeans.produce(new UnremovableBeanBuildItem(new Predicate<BeanInfo>() {
+                    @Override
+                    public boolean test(BeanInfo bean) {
+                        return bean.isProducerMethod() && bean.getTarget().get().asMethod().name().equals(name);
+                    }
+                }));
+            }
+
             ResultHandle staticMap = producer
                     .readStaticField(FieldDescriptor.of(ArcDeploymentTemplate.class, "supplierMap", Map.class));
             ResultHandle supplier = producer.invokeInterfaceMethod(
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java
index 6c0ff61e9de..f912972a9b2 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java
@@ -29,11 +29,11 @@
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.InstanceHandle;
 import io.quarkus.arc.ManagedContext;
+import io.quarkus.runtime.RuntimeValue;
 import io.quarkus.runtime.ShutdownContext;
 import io.quarkus.runtime.annotations.Template;
 
 /**
- * @author Martin Kouba
  */
 @Template
 public class ArcDeploymentTemplate {
@@ -64,7 +64,6 @@ public BeanContainer initBeanContainer(ArcContainer container, List<BeanContaine
             Collection<String> removedBeanTypes)
             throws Exception {
         BeanContainer beanContainer = new BeanContainer() {
-            @SuppressWarnings("unchecked")
             @Override
             public <T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers) {
                 Supplier<InstanceHandle<T>> handleSupplier = container.instanceSupplier(type, qualifiers);
@@ -122,6 +121,15 @@ public void run() {
         });
     }
 
+    public Supplier<Object> createSupplier(RuntimeValue<?> value) {
+        return new Supplier<Object>() {
+            @Override
+            public Object get() {
+                return value.getValue();
+            }
+        };
+    }
+
     private static final class DefaultInstanceFactory<T> implements BeanContainer.Factory<T> {
 
         final Class<T> type;
diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java
index 0174703ab91..8074c09b887 100644
--- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java
+++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java
@@ -23,10 +23,8 @@
 import org.jboss.jandex.ClassInfo;
 import org.jboss.jandex.DotName;
 
-import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
-import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
+import io.quarkus.arc.deployment.RuntimeBeanBuildItem;
 import io.quarkus.camel.core.runtime.CamelRuntime;
-import io.quarkus.camel.core.runtime.CamelRuntimeProducer;
 import io.quarkus.camel.core.runtime.CamelTemplate;
 import io.quarkus.camel.core.runtime.RuntimeRegistry;
 import io.quarkus.deployment.annotations.BuildProducer;
@@ -60,17 +58,10 @@ static class CamelConfig {
 
     CamelConfig config;
 
-    @Record(ExecutionTime.STATIC_INIT)
-    @BuildStep
-    AdditionalBeanBuildItem camelRuntimeProducer(BuildProducer<BeanContainerListenerBuildItem> listener, CamelTemplate template,
-            CamelRuntimeBuildItem runtimeBuildItem) {
-        listener.produce(new BeanContainerListenerBuildItem(template.initRuntimeInjection(runtimeBuildItem.getRuntime())));
-        return new AdditionalBeanBuildItem(CamelRuntimeProducer.class);
-    }
-
     @Record(ExecutionTime.STATIC_INIT)
     @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY })
-    CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTemplate template) {
+    CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTemplate template,
+            BuildProducer<RuntimeBeanBuildItem> runtimeBeans) {
         Properties properties = new Properties();
         Config configProvider = ConfigProvider.getConfig();
         for (String property : configProvider.getPropertyNames()) {
@@ -90,7 +81,12 @@ CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTempl
 
         visitServices((name, type) -> registry.bind(name, type, recorderContext.newInstance(type.getName())));
 
-        return new CamelRuntimeBuildItem(template.init(runtime, registry, properties, builders));
+        RuntimeValue<CamelRuntime> camelRuntime = template.init(runtime, registry, properties, builders);
+
+        runtimeBeans
+                .produce(RuntimeBeanBuildItem.builder(CamelRuntime.class).setRuntimeValue(camelRuntime).build());
+
+        return new CamelRuntimeBuildItem(camelRuntime);
     }
 
     @Record(ExecutionTime.RUNTIME_INIT)
diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java
index 375430ca5f5..f6c3898fa2d 100644
--- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java
+++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java
@@ -3,15 +3,17 @@
 import org.jboss.builder.item.SimpleBuildItem;
 
 import io.quarkus.camel.core.runtime.CamelRuntime;
+import io.quarkus.runtime.RuntimeValue;
 
 public final class CamelRuntimeBuildItem extends SimpleBuildItem {
-    private final CamelRuntime runtime;
 
-    public CamelRuntimeBuildItem(CamelRuntime runtime) {
+    private final RuntimeValue<CamelRuntime> runtime;
+
+    public CamelRuntimeBuildItem(RuntimeValue<CamelRuntime> runtime) {
         this.runtime = runtime;
     }
 
-    public CamelRuntime getRuntime() {
+    public RuntimeValue<CamelRuntime> getRuntime() {
         return runtime;
     }
 }
diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java
deleted file mode 100644
index 19c1e5be279..00000000000
--- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.quarkus.camel.core.runtime;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.inject.Produces;
-
-@ApplicationScoped
-public class CamelRuntimeProducer {
-
-    CamelRuntime camelRuntime;
-
-    @Produces
-    public CamelRuntime getCamelRuntime() {
-        return camelRuntime;
-    }
-
-    public void setCamelRuntime(CamelRuntime camelRuntime) {
-        this.camelRuntime = camelRuntime;
-    }
-}
diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java
index 2b8d873be9a..8b16ab640b2 100644
--- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java
+++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java
@@ -6,8 +6,6 @@
 
 import org.apache.camel.RoutesBuilder;
 
-import io.quarkus.arc.runtime.BeanContainer;
-import io.quarkus.arc.runtime.BeanContainerListener;
 import io.quarkus.runtime.RuntimeValue;
 import io.quarkus.runtime.ShutdownContext;
 import io.quarkus.runtime.annotations.Template;
@@ -15,7 +13,7 @@
 @Template
 public class CamelTemplate {
 
-    public CamelRuntime init(
+    public RuntimeValue<CamelRuntime> init(
             RuntimeValue<?> iruntime,
             RuntimeRegistry registry,
             Properties properties,
@@ -28,18 +26,18 @@ public CamelRuntime init(
                 .map(RoutesBuilder.class::cast)
                 .collect(Collectors.toList()));
         runtime.init();
-        return runtime;
+        return new RuntimeValue<>(runtime);
     }
 
-    public void start(final ShutdownContext shutdown, final CamelRuntime runtime) throws Exception {
-        runtime.start();
+    public void start(final ShutdownContext shutdown, final RuntimeValue<CamelRuntime> runtime) throws Exception {
+        runtime.getValue().start();
 
         //in development mode undertow is started eagerly
         shutdown.addShutdownTask(new Runnable() {
             @Override
             public void run() {
                 try {
-                    runtime.stop();
+                    runtime.getValue().stop();
                 } catch (Exception e) {
                     throw new RuntimeException(e);
                 }
@@ -47,12 +45,4 @@ public void run() {
         });
     }
 
-    public BeanContainerListener initRuntimeInjection(CamelRuntime runtime) {
-        return new BeanContainerListener() {
-            @Override
-            public void created(BeanContainer container) {
-                container.instance(CamelRuntimeProducer.class).setCamelRuntime(runtime);
-            }
-        };
-    }
 }
diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
index 8411f9e2dcb..0b5838a1dd3 100644
--- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
+++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java
@@ -76,11 +76,13 @@ void createExecutors(BuildProducer<RuntimeBeanBuildItem> runtimeBeanBuildItemBui
         Supplier<Object> boss = template.createEventLoop(1);
         Supplier<Object> worker = template.createEventLoop(0);
 
-        runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class.getName(), boss)
+        runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class)
+                .setSupplier(boss)
                 .setScope(ApplicationScoped.class)
                 .addQualifier(BossGroup.class)
                 .build());
-        runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class.getName(), worker)
+        runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class)
+                .setSupplier(worker)
                 .setScope(ApplicationScoped.class)
                 .build());
     }