From 32047ae811f21919ba452c027b1e300edcc3f581 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 4 Oct 2023 10:10:45 +0200 Subject: [PATCH 1/2] Config: detect injected config value mismatch during static init - record the values injected during static intialization phase - if the runtime value differs from the injected value the app startup fails - also introduce ExecutionMode to easily detect the STATIC_INIT bootstrap phase --- .../deployment/steps/MainClassBuildStep.java | 14 +++ .../io/quarkus/runtime/ExecutionMode.java | 30 ++++++ .../quarkus/runtime/ExecutionModeManager.java | 26 +++++ .../runtime/annotations/StaticInitSafe.java | 16 ++- .../arc/deployment/ConfigBuildStep.java | 2 +- .../ConfigStaticInitBuildSteps.java | 35 +++++++ .../config/staticinit/StaticInitBean.java | 20 ++++ .../StaticInitConfigInjectionFailureTest.java | 35 +++++++ .../staticinit/StaticInitEagerBean.java | 23 +++++ .../config/staticinit/StaticInitLazyBean.java | 22 +++++ .../config/staticinit/StaticInitSafeBean.java | 25 +++++ .../StaticInitSafeConfigInjectionTest.java | 32 ++++++ .../config/staticinit/UnsafeConfigSource.java | 30 ++++++ .../arc/runtime/ConfigBeanCreator.java | 2 + .../arc/runtime/ConfigStaticInitCheck.java | 19 ++++ .../ConfigStaticInitCheckInterceptor.java | 98 +++++++++++++++++++ .../arc/runtime/ConfigStaticInitValues.java | 94 ++++++++++++++++++ .../kotlin/RegisterCustomModuleCustomizer.kt | 3 +- 18 files changed, 520 insertions(+), 6 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/ExecutionMode.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/ExecutionModeManager.java create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigStaticInitBuildSteps.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitBean.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionFailureTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitEagerBean.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitLazyBean.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeBean.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeConfigInjectionTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/UnsafeConfigSource.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheck.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheckInterceptor.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 439210c22e07b..de47b02e92602 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -77,6 +77,7 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.runtime.Application; import io.quarkus.runtime.ApplicationLifecycleManager; +import io.quarkus.runtime.ExecutionModeManager; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder; import io.quarkus.runtime.PreventFurtherStepsException; @@ -106,6 +107,14 @@ public class MainClassBuildStep { void.class, StartupContext.class); public static final MethodDescriptor CONFIGURE_STEP_TIME_ENABLED = ofMethod(StepTiming.class.getName(), "configureEnabled", void.class); + public static final MethodDescriptor RUNTIME_EXECUTION_STATIC_INIT = ofMethod(ExecutionModeManager.class.getName(), + "staticInit", void.class); + public static final MethodDescriptor RUNTIME_EXECUTION_RUNTIME_INIT = ofMethod(ExecutionModeManager.class.getName(), + "runtimeInit", void.class); + public static final MethodDescriptor RUNTIME_EXECUTION_RUNNING = ofMethod(ExecutionModeManager.class.getName(), + "running", void.class); + public static final MethodDescriptor RUNTIME_EXECUTION_UNSET = ofMethod(ExecutionModeManager.class.getName(), + "unset", void.class); public static final MethodDescriptor CONFIGURE_STEP_TIME_START = ofMethod(StepTiming.class.getName(), "configureStart", void.class); private static final DotName QUARKUS_APPLICATION = DotName.createSimple(QuarkusApplication.class.getName()); @@ -170,6 +179,7 @@ void build(List staticInitTasks, lm); mv.invokeStaticMethod(CONFIGURE_STEP_TIME_ENABLED); + mv.invokeStaticMethod(RUNTIME_EXECUTION_STATIC_INIT); mv.invokeStaticMethod(ofMethod(Timing.class, "staticInitStarted", void.class, boolean.class), mv.load(launchMode.isAuxiliaryApplication())); @@ -227,6 +237,7 @@ void build(List staticInitTasks, mv.load(i.getKey()), mv.load(i.getValue())); } mv.invokeStaticMethod(ofMethod(NativeImageRuntimePropertiesRecorder.class, "doRuntime", void.class)); + mv.invokeStaticMethod(RUNTIME_EXECUTION_RUNTIME_INIT); // Set the SSL system properties if (!javaLibraryPathAdditionalPaths.isEmpty()) { @@ -268,6 +279,8 @@ void build(List staticInitTasks, loaders, constants, gizmoOutput, startupContext, tryBlock); } + tryBlock.invokeStaticMethod(RUNTIME_EXECUTION_RUNNING); + // Startup log messages List featureNames = new ArrayList<>(); for (FeatureBuildItem feature : features) { @@ -324,6 +337,7 @@ void build(List staticInitTasks, mv = file.getMethodCreator("doStop", void.class); mv.setModifiers(Modifier.PROTECTED | Modifier.FINAL); + mv.invokeStaticMethod(RUNTIME_EXECUTION_UNSET); startupContext = mv.readStaticField(scField.getFieldDescriptor()); mv.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext); mv.returnValue(null); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ExecutionMode.java b/core/runtime/src/main/java/io/quarkus/runtime/ExecutionMode.java new file mode 100644 index 0000000000000..5dfd5bb68d2d1 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ExecutionMode.java @@ -0,0 +1,30 @@ +package io.quarkus.runtime; + +/** + * The runtime execution mode. + */ +public enum ExecutionMode { + + /** + * Static initializiation. + */ + STATIC_INIT, + + /** + * Runtime initialization. + */ + RUNTIME_INIT, + + /** + * The application is running. + */ + RUNNING, + + UNSET, + ; + + public static ExecutionMode current() { + return ExecutionModeManager.getExecutionMode(); + } + +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ExecutionModeManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ExecutionModeManager.java new file mode 100644 index 0000000000000..18bf97a8c49b2 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ExecutionModeManager.java @@ -0,0 +1,26 @@ +package io.quarkus.runtime; + +public final class ExecutionModeManager { + + private static volatile ExecutionMode executionMode = ExecutionMode.UNSET; + + public static void staticInit() { + executionMode = ExecutionMode.STATIC_INIT; + } + + public static void runtimeInit() { + executionMode = ExecutionMode.RUNTIME_INIT; + } + + public static void running() { + executionMode = ExecutionMode.RUNNING; + } + + public static void unset() { + executionMode = ExecutionMode.UNSET; + } + + public static ExecutionMode getExecutionMode() { + return executionMode; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/StaticInitSafe.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/StaticInitSafe.java index 9e0eef1d411e3..1e7e956c84743 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/StaticInitSafe.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/StaticInitSafe.java @@ -1,5 +1,7 @@ package io.quarkus.runtime.annotations; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -7,10 +9,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import org.eclipse.microprofile.config.inject.ConfigProperty; + /** - * Used to mark a {@link org.eclipse.microprofile.config.spi.ConfigSource}, - * {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider} or {@link io.smallrye.config.ConfigSourceFactory} - * as safe to be initialized during STATIC INIT. + * Used to mark a configuration object as safe to be initialized during the STATIC INIT phase. + *

+ * The target configuration objects include {@link org.eclipse.microprofile.config.spi.ConfigSource}, + * {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider}, {@link io.smallrye.config.ConfigSourceFactory} and + * {@link io.smallrye.config.ConfigMapping}. Moreover, this annotation can be used for + * {@link org.eclipse.microprofile.config.inject.ConfigProperty} injection points. + *

* * When a Quarkus application is starting up, Quarkus will execute first a static init method which contains some * extensions actions and configurations. Example: @@ -36,7 +44,7 @@ * previous code example and a ConfigSource that requires database access. In this case, it is impossible to properly * initialize such ConfigSource, because the database services are not yet available so the ConfigSource in unusable. */ -@Target(TYPE) +@Target({ TYPE, FIELD, PARAMETER }) @Retention(RUNTIME) @Documented public @interface StaticInitSafe { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index af7ad71af9a81..f84a334724aa7 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -83,7 +83,7 @@ public class ConfigBuildStep { private static final Logger LOGGER = Logger.getLogger(ConfigBuildStep.class.getName()); private static final DotName MP_CONFIG = DotName.createSimple(Config.class.getName()); - private static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); + static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigStaticInitBuildSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigStaticInitBuildSteps.java new file mode 100644 index 0000000000000..19bb844f1a9ae --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigStaticInitBuildSteps.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.deployment; + +import org.jboss.jandex.DotName; + +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.runtime.ConfigStaticInitCheck; +import io.quarkus.arc.runtime.ConfigStaticInitCheckInterceptor; +import io.quarkus.arc.runtime.ConfigStaticInitValues; +import io.quarkus.deployment.annotations.BuildStep; + +public class ConfigStaticInitBuildSteps { + + @BuildStep + AdditionalBeanBuildItem registerBeans() { + return AdditionalBeanBuildItem.builder() + .addBeanClasses(ConfigStaticInitCheckInterceptor.class, ConfigStaticInitValues.class, + ConfigStaticInitCheck.class) + .build(); + } + + @BuildStep + AnnotationsTransformerBuildItem transformConfigProducer() { + DotName configProducerName = DotName.createSimple("io.smallrye.config.inject.ConfigProducer"); + + return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToMethod().whenMethod(m -> { + // Apply to all producer methods declared on io.smallrye.config.inject.ConfigProducer + return m.declaringClass().name().equals(configProducerName) + && m.hasAnnotation(DotNames.PRODUCES) + && m.hasAnnotation(ConfigBuildStep.MP_CONFIG_PROPERTY_NAME); + }).thenTransform(t -> { + t.add(ConfigStaticInitCheck.class); + })); + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitBean.java new file mode 100644 index 0000000000000..c9e48eee97d74 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitBean.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.test.config.staticinit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Singleton +public class StaticInitBean { + + @ConfigProperty(name = "apfelstrudel") + String value; + + // bean is instantiated during STATIC_INIT + void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) { + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionFailureTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionFailureTest.java new file mode 100644 index 0000000000000..fbcb1e352bdb2 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionFailureTest.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.test.config.staticinit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class StaticInitConfigInjectionFailureTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(StaticInitBean.class, StaticInitEagerBean.class, UnsafeConfigSource.class) + .addAsServiceProvider(ConfigSource.class, UnsafeConfigSource.class) + // the value from application.properties should be injected during STATIC_INIT + .addAsResource(new StringAsset("apfelstrudel=jandex"), "application.properties")) + .assertException(t -> { + assertThat(t).isInstanceOf(IllegalStateException.class) + .hasMessageContainingAll( + "A runtime config property value differs from the value that was injected during the static intialization phase", + "the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitBean#value", + "the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitEagerBean#value"); + }); + + @Test + public void test() { + fail(); + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitEagerBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitEagerBean.java new file mode 100644 index 0000000000000..dd15dd8399e8f --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitEagerBean.java @@ -0,0 +1,23 @@ +package io.quarkus.arc.test.config.staticinit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Singleton +public class StaticInitEagerBean { + + @ConfigProperty(name = "apfelstrudel") + Instance value; + + // bean is instantiated during STATIC_INIT + void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) { + // this should trigger the failure + value.get(); + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitLazyBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitLazyBean.java new file mode 100644 index 0000000000000..0a56799519d9a --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitLazyBean.java @@ -0,0 +1,22 @@ +package io.quarkus.arc.test.config.staticinit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Singleton +public class StaticInitLazyBean { + + @ConfigProperty(name = "apfelstrudel") + Instance value; + + // bean is instantiated during STATIC_INIT + void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) { + // value is not obtained... + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeBean.java new file mode 100644 index 0000000000000..d8ef1a03fea38 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeBean.java @@ -0,0 +1,25 @@ +package io.quarkus.arc.test.config.staticinit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.annotations.StaticInitSafe; + +@Singleton +public class StaticInitSafeBean { + + String value; + + public StaticInitSafeBean(@StaticInitSafe @ConfigProperty(name = "apfelstrudel") String value) { + this.value = value; + } + + // bean is instantiated during STATIC_INIT + void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) { + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeConfigInjectionTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeConfigInjectionTest.java new file mode 100644 index 0000000000000..fa910806570a3 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitSafeConfigInjectionTest.java @@ -0,0 +1,32 @@ +package io.quarkus.arc.test.config.staticinit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class StaticInitSafeConfigInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(StaticInitSafeBean.class, StaticInitLazyBean.class, UnsafeConfigSource.class) + .addAsServiceProvider(ConfigSource.class, UnsafeConfigSource.class) + // the value from application.properties should be injected during STATIC_INIT + .addAsResource(new StringAsset("apfelstrudel=jandex"), "application.properties")); + + @Inject + StaticInitSafeBean bean; + + @Test + public void test() { + assertEquals("jandex", bean.value); + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/UnsafeConfigSource.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/UnsafeConfigSource.java new file mode 100644 index 0000000000000..a8b5181610846 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/UnsafeConfigSource.java @@ -0,0 +1,30 @@ +package io.quarkus.arc.test.config.staticinit; + +import java.util.Set; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +// Intentionally not annotated with @StaticInitSafe so that it's not considered durin the STATIC_INIT +public class UnsafeConfigSource implements ConfigSource { + + @Override + public Set getPropertyNames() { + return Set.of("apfelstrudel"); + } + + @Override + public String getValue(String propertyName) { + return propertyName.equals("apfelstrudel") ? "gizmo" : null; + } + + @Override + public String getName() { + return "Unsafe Test"; + } + + @Override + public int getOrdinal() { + return 500; + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java index 4d0fdaf4efaad..548d2afe985c2 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java @@ -32,6 +32,8 @@ public Object create(CreationalContext creationalContext, Map) injectionPoint.getType()).isPrimitive()) { + if (injectionPoint.getType() == char.class) { + return null; + } else if (injectionPoint.getType() == boolean.class) { + return "false"; + } else { + return "0"; + } + } + return null; + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java new file mode 100644 index 0000000000000..f464416e7603f --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java @@ -0,0 +1,94 @@ +package io.quarkus.arc.runtime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jakarta.annotation.Priority; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.AnnotatedConstructor; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.ConfigValue; + +import io.quarkus.runtime.StartupEvent; +import io.smallrye.config.SmallRyeConfig; + +/** + * This is a store for all config values injected (directly or programmatically) during the static init phase. + *

+ * The values are then compared with the current values during the runtime init, i.e. when the application starts. If a mismatch + * is found the startup fails with an actionable error. + */ +@Singleton +public class ConfigStaticInitValues { + + private final List injectedValues = Collections.synchronizedList(new ArrayList<>()); + + void recordConfigValue(InjectionPoint injectionPoint, String name, String value) { + injectedValues.add(new InjectedValue(injectionPoint, name, value)); + } + + void onStart(@Observes @Priority(Integer.MIN_VALUE) StartupEvent event) { + if (injectedValues.isEmpty()) { + // No config values were recorded during static init phase + return; + } + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); + List mismatches = new ArrayList<>(); + for (InjectedValue injectedValue : injectedValues) { + ConfigValue currentValue = config.getConfigValue(injectedValue.name); + if (currentValue.getValue() != null && !injectedValue.value.equals(currentValue.getValue())) { + mismatches.add( + " - the runtime value of '" + injectedValue.name + "' is [" + currentValue.getValue() + + "] but the value [" + injectedValue.value + + "] was injected into " + + injectedValue.injectionPointInfo); + } + } + injectedValues.clear(); + if (!mismatches.isEmpty()) { + throw new IllegalStateException( + "A runtime config property value differs from the value that was injected during the static intialization phase:\n" + + String.join("\n", mismatches) + + "\n\nIf that's intentional then annotate the injected field/parameter with @io.quarkus.runtime.annotations.StaticInitSafe to eliminate the false positive."); + } + } + + private static class InjectedValue { + + private final String injectionPointInfo; + private final String name; + private final String value; + + private InjectedValue(InjectionPoint injectionPoint, String name, String value) { + this.injectionPointInfo = injectionPointToString(injectionPoint); + this.name = name; + this.value = value; + } + + } + + private static String injectionPointToString(InjectionPoint injectionPoint) { + Annotated annotated = injectionPoint.getAnnotated(); + if (annotated instanceof AnnotatedField) { + AnnotatedField field = (AnnotatedField) annotated; + return field.getDeclaringType().getJavaClass().getName() + "#" + field.getJavaMember().getName(); + } else if (annotated instanceof AnnotatedParameter) { + AnnotatedParameter param = (AnnotatedParameter) annotated; + if (param.getDeclaringCallable() instanceof AnnotatedConstructor) { + return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "()"; + } else { + return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "#" + + param.getDeclaringCallable().getJavaMember().getName() + "()"; + } + } + return injectionPoint.toString(); + } + +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt index 1600498fb6ad6..41605c9d645e7 100644 --- a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/RegisterCustomModuleCustomizer.kt @@ -4,10 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper import io.quarkus.jackson.ObjectMapperCustomizer import jakarta.inject.Singleton import org.eclipse.microprofile.config.inject.ConfigProperty +import io.quarkus.runtime.annotations.StaticInitSafe @Singleton class RegisterCustomModuleCustomizer : ObjectMapperCustomizer { - @ConfigProperty(name = "test.prop") lateinit var testProp: String + @StaticInitSafe @ConfigProperty(name = "test.prop") lateinit var testProp: String override fun customize(objectMapper: ObjectMapper) { GreetingResource.MY_PROPERTY.set(testProp) From 60c24cb86c566b9ee898cc3aa38ff95b6e0223ea Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 4 Oct 2023 11:23:46 +0200 Subject: [PATCH 2/2] Config: remove the warning for ConfigInjectionStaticInitBuildItem - deprecate ConfigInjectionStaticInitBuildItem --- .../arc/deployment/ConfigBuildStep.java | 68 ------------ .../ConfigInjectionStaticInitBuildItem.java | 5 + .../deployment/ResteasyCommonProcessor.java | 6 - .../ProviderConfigInjectionWarningsTest.java | 104 ------------------ .../deployment/UndertowBuildStep.java | 6 - .../WebFilterConfigInjectionWarningsTest.java | 94 ---------------- 6 files changed, 5 insertions(+), 278 deletions(-) delete mode 100644 extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/config/ProviderConfigInjectionWarningsTest.java delete mode 100644 extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/config/WebFilterConfigInjectionWarningsTest.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index f84a334724aa7..bffd54d9b832c 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -30,7 +30,6 @@ import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.CreationException; -import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigValue; import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -42,10 +41,8 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import org.jboss.logging.Logger; import io.quarkus.arc.Unremovable; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem; @@ -67,7 +64,6 @@ import io.quarkus.deployment.builditem.ConfigPropertiesBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.definition.RootDefinition; import io.quarkus.deployment.recording.RecorderContext; @@ -80,14 +76,11 @@ * MicroProfile Config related build steps. */ public class ConfigBuildStep { - private static final Logger LOGGER = Logger.getLogger(ConfigBuildStep.class.getName()); - private static final DotName MP_CONFIG = DotName.createSimple(Config.class.getName()); static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); - private static final DotName SR_CONFIG = DotName.createSimple(io.smallrye.config.SmallRyeConfig.class.getName()); private static final DotName SR_CONFIG_VALUE_NAME = DotName.createSimple(io.smallrye.config.ConfigValue.class.getName()); private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName()); @@ -474,67 +467,6 @@ void validateConfigPropertiesInjectionPoints( toRegister.forEach(configProperties::produce); } - @BuildStep - void warnStaticInitInjectionPoints( - CombinedIndexBuildItem indexBuildItem, - ValidationPhaseBuildItem validationPhase, - List configClasses, - List configInjectionStaticInit, - BuildProducer runTimeConfigurationDefault) { - - // Add here annotated classes that are initialized during static init - Set declaringClassCandidates = configInjectionStaticInit.stream() - .map(ConfigInjectionStaticInitBuildItem::getDeclaringCandidate).collect(toSet()); - - Set configClassesTypes = configClasses.stream().map(ConfigClassBuildItem::getTypes).flatMap(Collection::stream) - .collect(toSet()); - - for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) { - if (injectionPoint.getType().name().equals(DotNames.INSTANCE)) { - continue; - } - - Type type = Type.create(injectionPoint.getRequiredType().name(), Type.Kind.CLASS); - DotName injectionTypeName = null; - if (type.name().equals(MP_CONFIG) || type.name().equals(SR_CONFIG)) { - injectionTypeName = type.name(); - } else if (injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTY_NAME) != null) { - injectionTypeName = MP_CONFIG_PROPERTY_NAME; - } else if (configClassesTypes.contains(type)) { - injectionTypeName = type.name(); - } - - if (injectionTypeName != null) { - AnnotationTarget target = injectionPoint.getTarget(); - if (FIELD.equals(target.kind())) { - FieldInfo field = target.asField(); - ClassInfo declaringClass = field.declaringClass(); - Map> annotationsMap = declaringClass.annotationsMap(); - for (DotName declaringClassCandidate : declaringClassCandidates) { - List annotationInstances = annotationsMap.get(declaringClassCandidate); - if (annotationInstances != null && annotationInstances.size() == 1) { - AnnotationInstance annotationInstance = annotationInstances.get(0); - if (annotationInstance.target().equals(declaringClass)) { - LOGGER.warn("Directly injecting a " + - injectionTypeName + - " into a " + - declaringClassCandidate + - " may lead to unexpected results. To ensure proper results, please " + - "change the type of the field to " + - ParameterizedType.create(DotNames.INSTANCE, new Type[] { type }, null) + - ". Offending field is '" + - field.name() + - "' of class '" + - field.declaringClass() + - "'"); - } - } - } - } - } - } - } - @BuildStep @Record(RUNTIME_INIT) void registerConfigClasses( diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java index efb1919e33b02..78be1302d7c80 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigInjectionStaticInitBuildItem.java @@ -4,6 +4,11 @@ import io.quarkus.builder.item.MultiBuildItem; +/** + * + * @deprecated This build item is not used anymore + */ +@Deprecated(forRemoval = true) public final class ConfigInjectionStaticInitBuildItem extends MultiBuildItem { private final DotName declaringCandidate; diff --git a/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java b/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java index de8247f62696b..b78d5eec48b58 100644 --- a/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java @@ -43,7 +43,6 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; -import io.quarkus.arc.deployment.ConfigInjectionStaticInitBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.Capabilities; @@ -181,11 +180,6 @@ ResteasyInjectionReadyBuildItem setupResteasyInjection( return new ResteasyInjectionReadyBuildItem(injectorFactory); } - @BuildStep - ConfigInjectionStaticInitBuildItem configInjectionStaticInitProvider() { - return new ConfigInjectionStaticInitBuildItem(ResteasyDotNames.PROVIDER); - } - @BuildStep JaxrsProvidersToRegisterBuildItem setupProviders(BuildProducer reflectiveClass, CombinedIndexBuildItem indexBuildItem, diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/config/ProviderConfigInjectionWarningsTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/config/ProviderConfigInjectionWarningsTest.java deleted file mode 100644 index 305dc9592f2f2..0000000000000 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/config/ProviderConfigInjectionWarningsTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package io.quarkus.resteasy.test.config; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.Collectors; - -import jakarta.enterprise.inject.Instance; -import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.ext.ContextResolver; -import jakarta.ws.rs.ext.Provider; -import jakarta.ws.rs.ext.Providers; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.annotations.StaticInitSafe; -import io.quarkus.test.QuarkusUnitTest; -import io.restassured.RestAssured; -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.WithDefault; - -public class ProviderConfigInjectionWarningsTest { - - @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest() - .setLogRecordPredicate(record -> record.getLevel().intValue() >= Level.WARNING.intValue()) - .assertLogRecords(logRecords -> { - Set messages = logRecords.stream() - .map(LogRecord::getMessage) - // filter out noise coming from RESTEasy - .filter(m -> !m.contains("RESTEASY004687")) - .collect(Collectors.toSet()); - - assertEquals(4, messages.size()); - assertTrue(messages.contains( - "Directly injecting a org.eclipse.microprofile.config.Config into a jakarta.ws.rs.ext.Provider may lead to unexpected results. To ensure proper results, please change the type of the field to jakarta.enterprise.inject.Instance. Offending field is 'config' of class 'io.quarkus.resteasy.test.config.ProviderConfigInjectionWarningsTest$FooProvider'")); - assertTrue(messages.contains( - "Directly injecting a io.smallrye.config.SmallRyeConfig into a jakarta.ws.rs.ext.Provider may lead to unexpected results. To ensure proper results, please change the type of the field to jakarta.enterprise.inject.Instance. Offending field is 'smallRyeConfig' of class 'io.quarkus.resteasy.test.config.ProviderConfigInjectionWarningsTest$FooProvider'")); - assertTrue(messages.contains( - "Directly injecting a org.eclipse.microprofile.config.inject.ConfigProperty into a jakarta.ws.rs.ext.Provider may lead to unexpected results. To ensure proper results, please change the type of the field to jakarta.enterprise.inject.Instance. Offending field is 'configProperty' of class 'io.quarkus.resteasy.test.config.ProviderConfigInjectionWarningsTest$FooProvider'")); - assertTrue(messages.contains( - "Directly injecting a io.quarkus.resteasy.test.config.ProviderConfigInjectionWarningsTest$MyConfigMapping into a jakarta.ws.rs.ext.Provider may lead to unexpected results. To ensure proper results, please change the type of the field to jakarta.enterprise.inject.Instance. Offending field is 'myConfigMapping' of class 'io.quarkus.resteasy.test.config.ProviderConfigInjectionWarningsTest$FooProvider'")); - }); - - @Test - public void configWarnings() { - RestAssured.when().get("/test").then().body(Matchers.is("foo")); - } - - @Path("/test") - public static class TestResource { - @Context - private Providers providers; - - @GET - public String getFoo() { - return providers.getContextResolver(String.class, MediaType.TEXT_PLAIN_TYPE).getContext(null); - } - } - - @Provider - public static class FooProvider implements ContextResolver { - @Inject - Config config; - @Inject - Instance configInstance; - @Inject - SmallRyeConfig smallRyeConfig; - @Inject - Instance smallRyeConfigInstance; - @ConfigProperty(name = "configProperty", defaultValue = "configProperty") - String configProperty; - @ConfigProperty(name = "configProperty", defaultValue = "configProperty") - Instance stringInstance; - @Inject - MyConfigMapping myConfigMapping; - @Inject - Instance myConfigMappingInstance; - - @Override - public String getContext(Class type) { - return "foo"; - } - } - - @StaticInitSafe - @ConfigMapping(prefix = "my.mapping") - public interface MyConfigMapping { - @WithDefault("myProperty") - String myProperty(); - } -} diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index 06fd8dd5d7169..415204b0d4fc2 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -81,7 +81,6 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; -import io.quarkus.arc.deployment.ConfigInjectionStaticInitBuildItem; import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem; import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem.ContextConfiguratorBuildItem; import io.quarkus.arc.deployment.CustomScopeBuildItem; @@ -685,11 +684,6 @@ SyntheticBeanBuildItem servletContextBean( .supplier(recorder.servletContextSupplier()).done(); } - @BuildStep - ConfigInjectionStaticInitBuildItem configInjectionStaticInitAnnotations() { - return new ConfigInjectionStaticInitBuildItem(WEB_FILTER); - } - /** * Process a single index. * diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/config/WebFilterConfigInjectionWarningsTest.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/config/WebFilterConfigInjectionWarningsTest.java deleted file mode 100644 index a6e2a3e04bec4..0000000000000 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/config/WebFilterConfigInjectionWarningsTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.quarkus.undertow.test.config; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.stream.Collectors; - -import jakarta.enterprise.inject.Instance; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.annotation.WebFilter; -import jakarta.servlet.annotation.WebServlet; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.annotations.StaticInitSafe; -import io.quarkus.test.QuarkusUnitTest; -import io.restassured.RestAssured; -import io.smallrye.config.common.MapBackedConfigSource; - -public class WebFilterConfigInjectionWarningsTest { - @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest() - .withApplicationRoot(jar -> jar.addAsServiceProvider("org.eclipse.microprofile.config.spi.ConfigSource", - StaticConfigSource.class.getName(), RuntimeConfigSource.class.getName())) - .setLogRecordPredicate(record -> record.getLevel().intValue() >= Level.WARNING.intValue()) - .assertLogRecords(logRecords -> { - assertEquals(1, logRecords.size()); - Set messages = logRecords.stream().map(LogRecord::getMessage).collect(Collectors.toSet()); - assertTrue(messages.contains( - "Directly injecting a org.eclipse.microprofile.config.inject.ConfigProperty into a jakarta.servlet" + - ".annotation.WebFilter may lead to unexpected results. To ensure proper results, please change the " - + - "type of the field to jakarta.enterprise.inject.Instance. Offending field is " - + - "'configProperty' of class 'io.quarkus.undertow.test.config" + - ".WebFilterConfigInjectionWarningsTest$ConfigFilter'")); - }); - - @Test - public void configWarnings() { - RestAssured.when().get("/config").then().body(Matchers.is("static runtime")); - } - - @WebFilter(filterName = "ConfigFilter", servletNames = "config") - public static class ConfigFilter implements Filter { - @ConfigProperty(name = "configProperty", defaultValue = "configProperty") - String configProperty; - @ConfigProperty(name = "configProperty", defaultValue = "configProperty") - Instance configPropertyInstance; - - @Override - public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, - final FilterChain filterChain) throws IOException, ServletException { - filterChain.doFilter(servletRequest, servletResponse); - servletResponse.getWriter().write(configProperty + " " + configPropertyInstance.get()); - } - } - - @WebServlet(name = "config", urlPatterns = "/config") - public static class ConfigServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.getWriter().write(""); - } - } - - @StaticInitSafe - public static class StaticConfigSource extends MapBackedConfigSource { - public StaticConfigSource() { - super("static", Map.of("configProperty", "static"), 100); - } - } - - public static class RuntimeConfigSource extends MapBackedConfigSource { - public RuntimeConfigSource() { - super("runtime", Map.of("configProperty", "runtime"), 200); - } - } -}