From 65c2a0f175b44cb2c320e8b83e77889d71955d16 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 19 Jul 2022 00:28:24 +0100 Subject: [PATCH] Properly reload BuildTimeRunTime values in Dev mode --- .../BuildTimeConfigurationReader.java | 42 +++++++ .../RunTimeConfigurationGenerator.java | 92 ++------------ .../steps/ConfigGenerationBuildStep.java | 90 ++++++++------ .../runtime/configuration/ConfigRecorder.java | 47 +++---- .../runtime/configuration/ConfigUtils.java | 32 ++--- .../configuration/QuarkusConfigValue.java | 117 ++++++++++++++++++ .../config/BuildTimeRunTimeConfigTest.java | 106 ++++++++++++++++ .../io/quarkus/config/RenameConfigTest.java | 2 +- .../io/quarkus/extest/ConfiguredBeanTest.java | 2 +- .../extest/OverrideBuildTimeConfigTest.java | 25 ++++ .../io/quarkus/extest/UnknownConfigTest.java | 4 +- 11 files changed, 398 insertions(+), 161 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigValue.java create mode 100644 integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/BuildTimeRunTimeConfigTest.java create mode 100644 integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/OverrideBuildTimeConfigTest.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java index d0a2925aca491d..c048e4d6f75c77 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -362,6 +362,9 @@ ReadResult run() { nameBuilder.setLength(0); } + allBuildTimeValues.putAll(getDefaults(buildTimePatternMap)); + buildTimeRunTimeVisibleValues.putAll(getDefaults(buildTimeRunTimePatternMap)); + SmallRyeConfig runtimeDefaultsConfig = getConfigForRuntimeDefaults(); Set registeredRoots = allRoots.stream().map(RootDefinition::getPrefix).collect(toSet()); Set allProperties = getAllProperties(registeredRoots); @@ -929,6 +932,45 @@ private Map filterActiveProfileProperties(final Map getDefaults(final ConfigPatternMap patternMap) { + Map defaultValues = new TreeMap<>(); + getDefaults(defaultValues, new StringBuilder(), patternMap); + return defaultValues; + } + + private void getDefaults( + final Map defaultValues, + final StringBuilder propertyName, + final ConfigPatternMap patternMap) { + + Container matched = patternMap.getMatched(); + if (matched != null) { + ClassDefinition.ClassMember member = matched.getClassMember(); + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + String defaultValue = itemMember.getDefaultValue(); + if (defaultValue != null) { + // lookup config to make sure we catch relocates or fallbacks + ConfigValue configValue = config.getConfigValue(propertyName.toString()); + if (configValue.getValue() != null) { + defaultValues.put(configValue.getName(), configValue.getValue()); + } else { + defaultValues.put(configValue.getName(), defaultValue); + } + } + } + + if (propertyName.length() != 0 && patternMap.childNames().iterator().hasNext()) { + propertyName.append("."); + } + + for (String childName : patternMap.childNames()) { + getDefaults(defaultValues, + new StringBuilder(propertyName).append(childName.equals(ConfigPatternMap.WILD_CARD) ? "*" : childName), + patternMap.getChild(childName)); + } + } } public static final class ReadResult { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index 9427194fd7f49c..a970c41abbedcf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -89,18 +89,10 @@ public final class RunTimeConfigurationGenerator { public static final String CONFIG_CLASS_NAME = "io.quarkus.runtime.generated.Config"; static final String BSDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BootstrapDefaultValuesConfigSource"; static final String RTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.RunTimeDefaultValuesConfigSource"; - static final String BTRTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BuildTimeRunTimeDefaultValuesConfigSource"; // member descriptors - - static final MethodDescriptor BTRTDVCS_NEW = MethodDescriptor.ofConstructor(BTRTDVCS_CLASS_NAME); - public static final FieldDescriptor C_INSTANCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "INSTANCE", CONFIG_CLASS_NAME); - static final FieldDescriptor C_BUILD_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "buildTimeConfigSource", - ConfigSource.class); - static final FieldDescriptor C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, - "buildTimeRunTimeDefaultsConfigSource", ConfigSource.class); public static final MethodDescriptor C_CREATE_BOOTSTRAP_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "createBootstrapConfig", CONFIG_CLASS_NAME); public static final MethodDescriptor C_ENSURE_INITIALIZED = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, @@ -388,29 +380,10 @@ public static final class GenerateOperation implements AutoCloseable { clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); clinitNameBuilder = clinit.newInstance(SB_NEW); - // create the map for build time config source - final ResultHandle buildTimeValues = clinit.newInstance(HM_NEW); - for (Map.Entry entry : buildTimeRunTimeVisibleValues.entrySet()) { - clinit.invokeVirtualMethod(HM_PUT, buildTimeValues, clinit.load(entry.getKey()), clinit.load(entry.getValue())); - } - // static field containing the instance of the class - is set when createBootstrapConfig is run cc.getFieldCreator(C_INSTANCE) .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); - // the build time config source field, to feed into the run time config - cc.getFieldCreator(C_BUILD_TIME_CONFIG_SOURCE) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - final ResultHandle buildTimeConfigSource = clinit.newInstance(PCS_NEW, buildTimeValues, - clinit.load("Build time config"), clinit.load(Integer.MAX_VALUE)); - clinit.writeStaticField(C_BUILD_TIME_CONFIG_SOURCE, buildTimeConfigSource); - - // the build time run time visible default values config source - cc.getFieldCreator(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - final ResultHandle buildTimeRunTimeDefaultValuesConfigSource = clinit.newInstance(BTRTDVCS_NEW); - clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, buildTimeRunTimeDefaultValuesConfigSource); - // the bootstrap default values config source if (!buildTimeReadResult.isBootstrapRootsEmpty()) { cc.getFieldCreator(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE) @@ -445,13 +418,9 @@ public static final class GenerateOperation implements AutoCloseable { // the build time config, which is for user use only (not used by us other than for loading converters) final ResultHandle buildTimeBuilder = clinit.invokeStaticMethod(CU_CONFIG_BUILDER_WITH_ADD_DISCOVERED, clinit.load(true), clinit.load(false), clinit.load(launchMode)); - final ResultHandle array = clinit.newArray(ConfigSource[].class, 3); - // build time values (recorded visible for runtime) - clinit.writeArrayValue(array, 0, buildTimeConfigSource); - // build time runtime defaults for Config Roots - clinit.writeArrayValue(array, 1, buildTimeRunTimeDefaultValuesConfigSource); + final ResultHandle array = clinit.newArray(ConfigSource[].class, 1); // runtime default values (recorded during build time) - clinit.writeArrayValue(array, 2, clinit.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); + clinit.writeArrayValue(array, 0, clinit.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, buildTimeBuilder, array); // add safe static sources @@ -519,25 +488,9 @@ public void run() { // at run time (when we're ready) we update the factory and then release the build time config installConfiguration(clinitConfig, clinit); if (liveReloadPossible) { - final ResultHandle buildTimeRunTimeDefaultValuesConfigSource = reinit - .readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE); - // create the map for build time config source - final ResultHandle buildTimeValues = reinit.newInstance(HM_NEW); - for (Map.Entry entry : buildTimeRunTimeVisibleValues.entrySet()) { - reinit.invokeVirtualMethod(HM_PUT, buildTimeValues, reinit.load(entry.getKey()), - reinit.load(entry.getValue())); - } - final ResultHandle buildTimeConfigSource = reinit.newInstance(PCS_NEW, buildTimeValues, - reinit.load("Build time config = Reloaded"), reinit.load(Integer.MAX_VALUE)); // the build time config, which is for user use only (not used by us other than for loading converters) final ResultHandle buildTimeBuilder = reinit.invokeStaticMethod(CU_CONFIG_BUILDER, reinit.load(true), reinit.load(launchMode)); - final ResultHandle array = reinit.newArray(ConfigSource[].class, 2); - // build time values - reinit.writeArrayValue(array, 0, buildTimeConfigSource); - // build time defaults - reinit.writeArrayValue(array, 1, buildTimeRunTimeDefaultValuesConfigSource); - reinit.invokeVirtualMethod(SRCB_WITH_SOURCES, buildTimeBuilder, array); // add safe static sources for (String runtimeConfigSource : staticConfigSources) { reinit.invokeStaticMethod(CU_ADD_SOURCE_PROVIDER, buildTimeBuilder, @@ -620,18 +573,12 @@ public void run() { // add in the custom sources that bootstrap config needs ResultHandle bootstrapConfigSourcesArray = null; if (bootstrapConfigSetupNeeded()) { - bootstrapConfigSourcesArray = readBootstrapConfig.newArray(ConfigSource[].class, 4); - // build time config (expanded values) - readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 0, - readBootstrapConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); + bootstrapConfigSourcesArray = readBootstrapConfig.newArray(ConfigSource[].class, 2); // specified run time config default values - readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 1, + readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 0, readBootstrapConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); - // build time run time visible default config source - readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 2, - readBootstrapConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); // bootstrap config default values - readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 3, + readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 1, readBootstrapConfig.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE)); // add bootstrap safe static sources @@ -670,21 +617,16 @@ public void run() { // add in our custom sources final ResultHandle runtimeConfigSourcesArray = readConfig.newArray(ConfigSource[].class, - bootstrapConfigSetupNeeded() ? 5 : 4); - // build time config (expanded values) - readConfig.writeArrayValue(runtimeConfigSourcesArray, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); + bootstrapConfigSetupNeeded() ? 3 : 2); // specified run time config default values - readConfig.writeArrayValue(runtimeConfigSourcesArray, 1, + readConfig.writeArrayValue(runtimeConfigSourcesArray, 0, readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); // run time config default values - readConfig.writeArrayValue(runtimeConfigSourcesArray, 2, + readConfig.writeArrayValue(runtimeConfigSourcesArray, 1, readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); - // build time run time visible default config source - readConfig.writeArrayValue(runtimeConfigSourcesArray, 3, - readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); if (bootstrapConfigSetupNeeded()) { // bootstrap config default values - readConfig.writeArrayValue(runtimeConfigSourcesArray, 4, + readConfig.writeArrayValue(runtimeConfigSourcesArray, 2, readConfig.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE)); } @@ -955,9 +897,6 @@ public void run() { // generate run time default values config source class generateDefaultValuesConfigSourceClass(runTimePatternMap, RTDVCS_CLASS_NAME); - - // generate build time run time visible default values config source class - generateDefaultValuesConfigSourceClass(buildTimeRunTimePatternMap, BTRTDVCS_CLASS_NAME); } private void configSweepLoop(MethodDescriptor parserBody, MethodCreator method, ResultHandle config, @@ -1741,6 +1680,7 @@ private void reportUnknown(final MethodCreator mc) { mc.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); ResultHandle unknownProperty = mc.getMethodParam(0); + ResultHandle unknown = mc.getMethodParam(1); // Ignore all build property names. This is to ignore any properties mapped with @ConfigMapping, because // these do not fall into the ignore ConfigPattern. @@ -1751,20 +1691,8 @@ private void reportUnknown(final MethodCreator mc) { mc.ifTrue(equalsResult).trueBranch().returnValue(null); } - ResultHandle unknown = mc.getMethodParam(1); - // Ignore recorded properties. If properties are present here, it means that they were recorded at build // time as being mapped in a @ConfigMapping - ResultHandle buildTimeRunTimeSource = mc.readStaticField(C_BUILD_TIME_CONFIG_SOURCE); - ResultHandle buildTimeRunTimeValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeSource, - unknownProperty); - mc.ifNotNull(buildTimeRunTimeValue).trueBranch().returnValue(null); - - ResultHandle buildTimeRunTimeDefaultsSource = mc.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE); - ResultHandle buildTimeRunTimeDefaultValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeDefaultsSource, - unknownProperty); - mc.ifNotNull(buildTimeRunTimeDefaultValue).trueBranch().returnValue(null); - ResultHandle runtimeSpecifiedSource = mc.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE); ResultHandle runtimeSpecifiedValue = mc.invokeInterfaceMethod(CS_GET_VALUE, runtimeSpecifiedSource, unknownProperty); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index be3f6406bcebb2..f6d0107ecb6c24 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -3,6 +3,7 @@ import static io.quarkus.deployment.configuration.ConfigMappingUtils.processExtensionConfigMapping; import static io.quarkus.deployment.steps.ConfigBuildSteps.SERVICES_PREFIX; import static io.quarkus.deployment.util.ServiceUtil.classNamesNamedIn; +import static io.quarkus.runtime.configuration.ConfigUtils.QUARKUS_BUILD_TIME_RUNTIME_PROPERTIES; import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_LOCATIONS; import static java.util.stream.Collectors.toList; @@ -29,6 +30,7 @@ import org.apache.commons.io.FilenameUtils; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.ConfigValue; import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.ConfigSourceProvider; @@ -59,18 +61,16 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; -import io.quarkus.deployment.configuration.definition.ClassDefinition; -import io.quarkus.deployment.configuration.definition.RootDefinition; -import io.quarkus.deployment.logging.LoggingSetupBuildItem; +import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.StaticInitSafe; import io.quarkus.runtime.configuration.ConfigDiagnostic; import io.quarkus.runtime.configuration.ConfigRecorder; import io.quarkus.runtime.configuration.ConfigUtils; import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.runtime.configuration.QuarkusConfigValue; import io.quarkus.runtime.configuration.RuntimeOverrideConfigSource; import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.ConfigSourceFactory; @@ -88,18 +88,39 @@ void staticInitSources( } @BuildStep - GeneratedResourceBuildItem runtimeDefaultsConfig(List runTimeDefaults, - BuildProducer nativeImageResourceBuildItemBuildProducer) - throws IOException { + void buildTimeRunTimeVisibleConfig( + ConfigurationBuildItem configItem, + BuildProducer generatedResource, + BuildProducer nativeImageResource) throws Exception { + + Map buildTimeRunTimeVisibleValues = configItem.getReadResult().getBuildTimeRunTimeVisibleValues(); + Properties properties = new Properties(); + for (Map.Entry entry : buildTimeRunTimeVisibleValues.entrySet()) { + properties.setProperty(entry.getKey(), entry.getValue()); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + properties.store(out, null); + generatedResource.produce(new GeneratedResourceBuildItem(QUARKUS_BUILD_TIME_RUNTIME_PROPERTIES, out.toByteArray())); + nativeImageResource.produce(new NativeImageResourceBuildItem(QUARKUS_BUILD_TIME_RUNTIME_PROPERTIES)); + } + + @BuildStep + void runtimeDefaultsConfig( + List runTimeDefaults, + BuildProducer generatedResource, + BuildProducer nativeImageResource) throws IOException { + Properties p = new Properties(); for (var e : runTimeDefaults) { p.setProperty(e.getKey(), e.getValue()); } + ByteArrayOutputStream out = new ByteArrayOutputStream(); p.store(out, null); - nativeImageResourceBuildItemBuildProducer - .produce(new NativeImageResourceBuildItem(ConfigUtils.QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES)); - return new GeneratedResourceBuildItem(ConfigUtils.QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES, out.toByteArray()); + generatedResource.produce( + new GeneratedResourceBuildItem(ConfigUtils.QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES, out.toByteArray())); + nativeImageResource.produce(new NativeImageResourceBuildItem(ConfigUtils.QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES)); } @BuildStep @@ -231,8 +252,14 @@ public void suppressNonRuntimeConfigChanged( @BuildStep @Record(ExecutionTime.RUNTIME_INIT) public void checkForBuildTimeConfigChange( - ConfigRecorder recorder, ConfigurationBuildItem configItem, LoggingSetupBuildItem loggingSetupBuildItem, + RecorderContext recorderContext, + ConfigRecorder recorder, + ConfigurationBuildItem configItem, List suppressNonRuntimeConfigChangedWarningItems) { + + recorderContext.registerSubstitution(io.smallrye.config.ConfigValue.class, QuarkusConfigValue.class, + QuarkusConfigValue.Substitution.class); + BuildTimeConfigurationReader.ReadResult readResult = configItem.getReadResult(); Config config = ConfigProvider.getConfig(); @@ -241,15 +268,22 @@ public void checkForBuildTimeConfigChange( excludedConfigKeys.add(item.getConfigKey()); } - Map values = new HashMap<>(); - for (RootDefinition root : readResult.getAllRoots()) { - if (root.getConfigPhase() == ConfigPhase.BUILD_AND_RUN_TIME_FIXED || - root.getConfigPhase() == ConfigPhase.BUILD_TIME) { + Map values = new HashMap<>(); + + for (final Map.Entry entry : readResult.getAllBuildTimeValues().entrySet()) { + if (excludedConfigKeys.contains(entry.getKey())) { + continue; + } + values.putIfAbsent(entry.getKey(), config.getConfigValue(entry.getKey())); + } - Iterable members = root.getMembers(); - handleMembers(config, values, members, root.getName() + ".", excludedConfigKeys); + for (Map.Entry entry : readResult.getBuildTimeRunTimeVisibleValues().entrySet()) { + if (excludedConfigKeys.contains(entry.getKey())) { + continue; } + values.put(entry.getKey(), config.getConfigValue(entry.getKey())); } + recorder.handleConfigChange(values); } @@ -308,28 +342,6 @@ private String appendProfileToFilename(String path, String activeProfile) { return String.format("%s-%s.%s", pathWithoutExtension, activeProfile, FilenameUtils.getExtension(path)); } - private void handleMembers(Config config, Map values, Iterable members, - String prefix, Set excludedConfigKeys) { - for (ClassDefinition.ClassMember member : members) { - if (member instanceof ClassDefinition.ItemMember) { - ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; - String propertyName = prefix + member.getPropertyName(); - if (excludedConfigKeys.contains(propertyName)) { - continue; - } - Optional val = config.getOptionalValue(propertyName, String.class); - if (val.isPresent()) { - values.put(propertyName, val.get()); - } else { - values.put(propertyName, itemMember.getDefaultValue()); - } - } else if (member instanceof ClassDefinition.GroupMember) { - handleMembers(config, values, ((ClassDefinition.GroupMember) member).getGroupDefinition().getMembers(), - prefix + member.getDescriptor().getName() + ".", excludedConfigKeys); - } - } - } - private static Set discoverService( Class serviceClass, BuildProducer reflectiveClass) throws IOException { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java index e898813ee45175..ff6ae5df6dba64 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java @@ -3,15 +3,16 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.ConfigValue; +import org.eclipse.microprofile.config.spi.ConfigSource; import org.jboss.logging.Logger; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigurationRuntimeConfig.BuildTimeMismatchAtRuntime; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; @Recorder public class ConfigRecorder { @@ -24,25 +25,30 @@ public ConfigRecorder(ConfigurationRuntimeConfig configurationConfig) { this.configurationConfig = configurationConfig; } - public void handleConfigChange(Map buildTimeConfig) { - Config configProvider = ConfigProvider.getConfig(); - List mismatches = null; - for (Map.Entry entry : buildTimeConfig.entrySet()) { - Optional val = configProvider.getOptionalValue(entry.getKey(), String.class); - if (val.isPresent()) { - if (!val.get().equals(entry.getValue())) { - if (mismatches == null) { - mismatches = new ArrayList<>(); - } - mismatches.add(" - " + entry.getKey() + " is set to '" + val.get() - + "' but it is build time fixed to '" + entry.getValue() + "'. Did you change the property " - + entry.getKey() + " after building the application?"); - } + public void handleConfigChange(Map buildTimeRuntimeValues) { + SmallRyeConfigBuilder configBuilder = ConfigUtils.emptyConfigBuilder(); + for (ConfigSource configSource : ConfigProvider.getConfig().getConfigSources()) { + if ("PropertiesConfigSource[source=BuildTimeRunTime]".equals(configSource.getName())) { + continue; } + configBuilder.withSources(configSource); } - if (mismatches != null && !mismatches.isEmpty()) { - final String msg = "Build time property cannot be changed at runtime:\n" - + mismatches.stream().collect(Collectors.joining("\n")); + SmallRyeConfig config = configBuilder.build(); + + List mismatches = new ArrayList<>(); + for (Map.Entry entry : buildTimeRuntimeValues.entrySet()) { + ConfigValue currentValue = config.getConfigValue(entry.getKey()); + if (currentValue.getValue() != null && !entry.getValue().getValue().equals(currentValue.getValue()) + && entry.getValue().getSourceOrdinal() < currentValue.getSourceOrdinal()) { + mismatches.add( + " - " + entry.getKey() + " is set to '" + currentValue.getValue() + + "' but it is build time fixed to '" + + entry.getValue().getValue() + "'. Did you change the property " + entry.getKey() + + " after building the application?"); + } + } + if (!mismatches.isEmpty()) { + final String msg = "Build time property cannot be changed at runtime:\n" + String.join("\n", mismatches); switch (configurationConfig.buildTimeMismatchAtRuntime) { case fail: throw new IllegalStateException(msg); @@ -53,7 +59,6 @@ public void handleConfigChange(Map buildTimeConfig) { throw new IllegalStateException("Unexpected " + BuildTimeMismatchAtRuntime.class.getName() + ": " + configurationConfig.buildTimeMismatchAtRuntime); } - } } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index 7cf026e96b83e5..efad759d8ee472 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -7,7 +7,6 @@ import static io.smallrye.config.SmallRyeConfigBuilder.META_INF_MICROPROFILE_CONFIG_PROPERTIES; import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -19,7 +18,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -57,6 +55,7 @@ public final class ConfigUtils { * The name of the property associated with a random UUID generated at launch time. */ static final String UUID_KEY = "quarkus.uuid"; + public static final String QUARKUS_BUILD_TIME_RUNTIME_PROPERTIES = "quarkus-build-time-runtime.properties"; public static final String QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES = "quarkus-runtime-config-defaults.properties"; private ConfigUtils() { @@ -112,6 +111,8 @@ public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final b builder.addDefaultSources(); builder.withDefaultValue(UUID_KEY, UUID.randomUUID().toString()); builder.withSources(new DotEnvConfigSourceProvider()); + builder.withSources( + new PropertiesConfigSource(loadBuildTimeRunTimeVisibleValues(), "BuildTimeRunTime", Integer.MAX_VALUE)); builder.withSources( new PropertiesConfigSource(loadRuntimeDefaultValues(), "Runtime Defaults", Integer.MIN_VALUE + 50)); } else { @@ -226,21 +227,20 @@ public static void addSourceFactoryProvider(SmallRyeConfigBuilder builder, Confi builder.withSources(provider.getConfigSourceFactory(Thread.currentThread().getContextClassLoader())); } + public static Map loadBuildTimeRunTimeVisibleValues() { + try { + URL resource = Thread.currentThread().getContextClassLoader().getResource(QUARKUS_BUILD_TIME_RUNTIME_PROPERTIES); + return resource != null ? ConfigSourceUtil.urlToMap(resource) : Collections.emptyMap(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static Map loadRuntimeDefaultValues() { - Map values = new HashMap<>(); - try (InputStream in = Thread.currentThread().getContextClassLoader() - .getResourceAsStream(QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES)) { - if (in == null) { - return values; - } - Properties p = new Properties(); - p.load(in); - for (String k : p.stringPropertyNames()) { - if (!values.containsKey(k)) { - values.put(k, p.getProperty(k)); - } - } - return values; + try { + URL resource = Thread.currentThread().getContextClassLoader() + .getResource(QUARKUS_RUNTIME_CONFIG_DEFAULTS_PROPERTIES); + return resource != null ? ConfigSourceUtil.urlToMap(resource) : Collections.emptyMap(); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigValue.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigValue.java new file mode 100644 index 00000000000000..8ff134fa354c03 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigValue.java @@ -0,0 +1,117 @@ +package io.quarkus.runtime.configuration; + +import io.quarkus.runtime.ObjectSubstitution; +import io.smallrye.config.ConfigValue; + +public class QuarkusConfigValue { + private String name; + private String value; + private String rawValue; + private String profile; + private String configSourceName; + private int configSourceOrdinal; + private int configSourcePosition; + private int lineNumber; + + public String getName() { + return name; + } + + public QuarkusConfigValue setName(final String name) { + this.name = name; + return this; + } + + public String getValue() { + return value; + } + + public QuarkusConfigValue setValue(final String value) { + this.value = value; + return this; + } + + public String getRawValue() { + return rawValue; + } + + public QuarkusConfigValue setRawValue(final String rawValue) { + this.rawValue = rawValue; + return this; + } + + public String getProfile() { + return profile; + } + + public QuarkusConfigValue setProfile(final String profile) { + this.profile = profile; + return this; + } + + public String getConfigSourceName() { + return configSourceName; + } + + public QuarkusConfigValue setConfigSourceName(final String configSourceName) { + this.configSourceName = configSourceName; + return this; + } + + public int getConfigSourceOrdinal() { + return configSourceOrdinal; + } + + public QuarkusConfigValue setConfigSourceOrdinal(final int configSourceOrdinal) { + this.configSourceOrdinal = configSourceOrdinal; + return this; + } + + public int getConfigSourcePosition() { + return configSourcePosition; + } + + public QuarkusConfigValue setConfigSourcePosition(final int configSourcePosition) { + this.configSourcePosition = configSourcePosition; + return this; + } + + public int getLineNumber() { + return lineNumber; + } + + public QuarkusConfigValue setLineNumber(final int lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + public static final class Substitution implements ObjectSubstitution { + @Override + public QuarkusConfigValue serialize(final ConfigValue obj) { + QuarkusConfigValue configValue = new QuarkusConfigValue(); + configValue.setName(obj.getName()); + configValue.setValue(obj.getValue()); + configValue.setRawValue(obj.getRawValue()); + configValue.setProfile(obj.getProfile()); + configValue.setConfigSourceName(obj.getConfigSourceName()); + configValue.setConfigSourceOrdinal(obj.getConfigSourceOrdinal()); + configValue.setConfigSourcePosition(obj.getConfigSourcePosition()); + configValue.setLineNumber(obj.getLineNumber()); + return configValue; + } + + @Override + public ConfigValue deserialize(final QuarkusConfigValue obj) { + return ConfigValue.builder() + .withName(obj.getName()) + .withValue(obj.getValue()) + .withRawValue(obj.getRawValue()) + .withProfile(obj.getProfile()) + .withConfigSourceName(obj.getConfigSourceName()) + .withConfigSourceOrdinal(obj.getConfigSourceOrdinal()) + .withConfigSourcePosition(obj.getConfigSourcePosition()) + .withLineNumber(obj.getLineNumber()) + .build(); + } + } +} diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/BuildTimeRunTimeConfigTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/BuildTimeRunTimeConfigTest.java new file mode 100644 index 00000000000000..3f9ff987d32508 --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/BuildTimeRunTimeConfigTest.java @@ -0,0 +1,106 @@ +package io.quarkus.config; + +import static org.hamcrest.core.Is.is; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.deployment.util.FileUtil; +import io.quarkus.runtime.ApplicationConfig; +import io.quarkus.runtime.StartupEvent; +import io.quarkus.runtime.TlsConfig; +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; +import io.smallrye.config.SmallRyeConfig; +import io.vertx.ext.web.Router; + +public class BuildTimeRunTimeConfigTest { + @RegisterExtension + static final QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> { + try { + String props = new String(FileUtil.readFileContents( + BuildTimeRunTimeConfigTest.class.getClassLoader().getResourceAsStream("application.properties")), + StandardCharsets.UTF_8); + + return ShrinkWrap.create(JavaArchive.class) + .addClasses(DevBean.class) + .addAsResource(new StringAsset(props + "\nquarkus.application.name=my-app\n"), + "application.properties"); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).setLogRecordPredicate(logRecord -> !logRecord.getMessage().contains("but it is build time fixed to")); + + @Test + void buildTimeRunTimeConfig() { + // A combination of QuarkusUnitTest and QuarkusProdModeTest tests ordering may mess with the port leaving it in + // 8081 and QuarkusDevModeTest does not changes to the right port. + RestAssured.port = -1; + + RestAssured.when().get("/application").then() + .statusCode(200) + .body(is("my-app")); + + RestAssured.when().get("/tls").then() + .statusCode(200) + .body(is("false")); + + RestAssured.when().get("/source/quarkus.application.name").then() + .statusCode(200) + .body(is("PropertiesConfigSource[source=BuildTimeRunTime]")); + + RestAssured.when().get("/source/quarkus.tls.trust-all").then() + .statusCode(200) + .body(is("PropertiesConfigSource[source=BuildTimeRunTime]")); + + TEST.modifyResourceFile("application.properties", s -> s + "\n" + + "quarkus.application.name=modified-app\n" + + "quarkus.tls.trust-all=true\n"); + + RestAssured.when().get("/application").then() + .statusCode(200) + .body(is("modified-app")); + + RestAssured.when().get("/tls").then() + .statusCode(200) + .body(is("true")); + + RestAssured.when().get("/source/quarkus.application.name").then() + .statusCode(200) + .body(is("PropertiesConfigSource[source=BuildTimeRunTime]")); + + RestAssured.when().get("/source/quarkus.tls.trust-all").then() + .statusCode(200) + .body(is("PropertiesConfigSource[source=BuildTimeRunTime]")); + } + + @ApplicationScoped + public static class DevBean { + @Inject + Router router; + @Inject + SmallRyeConfig config; + @Inject + ApplicationConfig applicationConfig; + @Inject + TlsConfig tlsConfig; + + public void register(@Observes StartupEvent ev) { + router.get("/application").handler(rc -> rc.response().end(applicationConfig.name.get())); + router.get("/tls").handler(rc -> rc.response().end(tlsConfig.trustAll + "")); + router.get("/source/:name") + .handler(rc -> rc.response().end(config.getConfigValue(rc.pathParam("name")).getConfigSourceName())); + } + } +} diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/RenameConfigTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/RenameConfigTest.java index 9a9de01184dedd..f02ed4cd7b354c 100644 --- a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/RenameConfigTest.java +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/RenameConfigTest.java @@ -52,7 +52,7 @@ void rename() { assertEquals("old-default", renameConfig.withDefault); // Make sure we only record the actual properties in the sources (and not renamed properties) - Optional configSource = config.getConfigSource("PropertiesConfigSource[source=Build time config]"); + Optional configSource = config.getConfigSource("PropertiesConfigSource[source=BuildTimeRunTime]"); assertTrue(configSource.isPresent()); ConfigSource buildTimeRunTimeDefaults = configSource.get(); diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java index 0810ca4b017b13..ae6f1a9544a6c0 100644 --- a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java @@ -320,7 +320,7 @@ public void buildTimeDefaults() { ConfigValue value = config.getConfigValue("quarkus.btrt.all-values.long-primitive"); Assertions.assertEquals("1234567891", value.getValue()); - Assertions.assertEquals("PropertiesConfigSource[source=Build time config]", value.getConfigSourceName()); + Assertions.assertEquals("PropertiesConfigSource[source=BuildTimeRunTime]", value.getConfigSourceName()); Assertions.assertEquals(Integer.MAX_VALUE, value.getConfigSourceOrdinal()); } diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/OverrideBuildTimeConfigTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/OverrideBuildTimeConfigTest.java new file mode 100644 index 00000000000000..4c0dae6a0dc655 --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/OverrideBuildTimeConfigTest.java @@ -0,0 +1,25 @@ +package io.quarkus.extest; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class OverrideBuildTimeConfigTest { + @RegisterExtension + static final QuarkusProdModeTest TEST = new QuarkusProdModeTest() + .setRuntimeProperties(Map.of("quarkus.tls.trust-all", "true")) + .setRun(true); + + @Test + void overrideBuildTimeConfigTest() { + assertTrue(TEST.getStartupConsoleOutput() + .contains( + "- quarkus.tls.trust-all is set to 'true' but it is build time fixed to 'false'. Did you " + + "change the property quarkus.tls.trust-all after building the application?")); + } +} diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/UnknownConfigTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/UnknownConfigTest.java index a29ad1cf240648..d9f2a3d07ef490 100644 --- a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/UnknownConfigTest.java +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/extest/UnknownConfigTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; @@ -27,7 +28,8 @@ public class UnknownConfigTest { .setLogRecordPredicate(record -> record.getLevel().intValue() >= Level.WARNING.intValue()) .assertLogRecords(logRecords -> { Set properties = logRecords.stream().flatMap( - logRecord -> Stream.of(logRecord.getParameters())).map(Object::toString).collect(Collectors.toSet()); + logRecord -> Stream.of(Optional.ofNullable(logRecord.getParameters()).orElse(new Object[0]))) + .map(Object::toString).collect(Collectors.toSet()); assertTrue(properties.contains("quarkus.unknown.prop")); assertFalse(properties.contains("quarkus.build.unknown.prop")); });