From 4a680de0933c4e1270b947ff86112f6868b421ac Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 22 Jan 2020 17:15:36 +0200 Subject: [PATCH] Add first version of bootstrap config support --- ci-templates/stages.yml | 3 +- .../quarkus/deployment/ExtensionLoader.java | 54 +++- ...tstrapConfigBytecodeRecorderBuildItem.java | 24 ++ ...TimeConfigurationSourceValueBuildItem.java | 29 +++ .../BuildTimeConfigurationReader.java | 47 +++- .../RunTimeConfigurationGenerator.java | 236 +++++++++++++----- .../definition/RootDefinition.java | 10 + .../steps/ConfigDescriptionBuildStep.java | 1 + .../deployment/steps/MainClassBuildStep.java | 101 ++++++-- .../runner/bootstrap/StartupActionImpl.java | 2 +- .../annotation/processor/Constants.java | 1 + .../processor/generate_doc/ConfigPhase.java | 16 ++ .../io/quarkus/runtime/StartupContext.java | 13 + .../runtime/annotations/ConfigPhase.java | 7 + .../runtime/configuration/ConfigUtils.java | 26 +- .../bootstrap-config/application/pom.xml | 100 ++++++++ .../it/bootstrap/config/GreetingResource.java | 18 ++ .../src/main/resources/application.properties | 2 + .../config/GreetingResourceTest.java | 23 ++ .../config/GreetingResourceTestIT.java | 7 + .../extension/deployment/pom.xml | 45 ++++ .../DummyBootstrapConfigBuildStep.java | 26 ++ .../bootstrap-config/extension/pom.xml | 23 ++ .../extension/runtime/pom.xml | 43 ++++ .../extension/DummyBootstrapRecorder.java | 15 ++ .../extension/DummyBootstrapRecorder2.java | 22 ++ .../config/extension/DummyConfig.java | 20 ++ .../extension/DummyConfigSourceProvider.java | 77 ++++++ integration-tests/bootstrap-config/pom.xml | 23 ++ integration-tests/pom.xml | 1 + 30 files changed, 906 insertions(+), 109 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBootstrapConfigBytecodeRecorderBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceValueBuildItem.java create mode 100644 integration-tests/bootstrap-config/application/pom.xml create mode 100644 integration-tests/bootstrap-config/application/src/main/java/io/quarkus/it/bootstrap/config/GreetingResource.java create mode 100644 integration-tests/bootstrap-config/application/src/main/resources/application.properties create mode 100644 integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTest.java create mode 100644 integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTestIT.java create mode 100644 integration-tests/bootstrap-config/extension/deployment/pom.xml create mode 100644 integration-tests/bootstrap-config/extension/deployment/src/main/java/io/quarkus/it/bootstrap/config/extension/deployment/DummyBootstrapConfigBuildStep.java create mode 100644 integration-tests/bootstrap-config/extension/pom.xml create mode 100644 integration-tests/bootstrap-config/extension/runtime/pom.xml create mode 100644 integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder.java create mode 100644 integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder2.java create mode 100644 integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfig.java create mode 100644 integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfigSourceProvider.java create mode 100644 integration-tests/bootstrap-config/pom.xml diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 814bbf6ad..2dba6ffe0 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -363,12 +363,13 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 30 + timeoutInMinutes: 35 modules: - tika - hibernate-validator - test-extension - logging-gelf + - bootstrap-config name: misc_2 - template: native-build-steps.yaml diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index a1bb53b30..66cb3f3c6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -66,8 +66,10 @@ import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; +import io.quarkus.deployment.builditem.MainBootstrapConfigBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource; @@ -352,6 +354,8 @@ public final class ExtensionLoader { if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); } + } else if (phase == ConfigPhase.BOOTSTRAP) { + throw reportError(parameter, "Bootstrap configuration cannot be consumed here"); } else if (phase == ConfigPhase.RUN_TIME) { throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { @@ -467,6 +471,8 @@ public final class ExtensionLoader { if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { runTimeProxies.computeIfAbsent(fieldClass, readResult::requireRootObjectForClass); } + } else if (phase == ConfigPhase.BOOTSTRAP) { + throw reportError(field, "Bootstrap configuration cannot be consumed here"); } else if (phase == ConfigPhase.RUN_TIME) { throw reportError(field, "Run time configuration cannot be consumed here"); } else { @@ -539,6 +545,8 @@ public final class ExtensionLoader { final ConfigPhase phase = annotation.phase(); if (phase.isAvailableAtBuild()) { paramSuppList.add(() -> readResult.requireRootObjectForClass(parameterClass)); + } else if (phase == ConfigPhase.BOOTSTRAP) { + throw reportError(parameter, "Bootstrap configuration cannot be consumed here"); } else if (phase == ConfigPhase.RUN_TIME) { throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { @@ -572,6 +580,8 @@ public final class ExtensionLoader { if (phase.isAvailableAtBuild()) { setup = setup.andThen(o -> ReflectUtil.setFieldVal(field, o, readResult.requireRootObjectForClass(fieldClass))); + } else if (phase == ConfigPhase.BOOTSTRAP) { + throw reportError(field, "Bootstrap configuration cannot be consumed here"); } else if (phase == ConfigPhase.RUN_TIME) { throw reportError(field, "Run time configuration cannot be consumed here"); } else { @@ -634,9 +644,10 @@ public final class ExtensionLoader { assert recordAnnotation != null; final ExecutionTime executionTime = recordAnnotation.value(); final boolean optional = recordAnnotation.optional(); + methodStepConfig = methodStepConfig.andThen(bsb -> bsb.produces( executionTime == ExecutionTime.STATIC_INIT ? StaticBytecodeRecorderBuildItem.class - : MainBytecodeRecorderBuildItem.class, + : determineMainRecorderBuildItemType(method), optional ? ProduceFlags.of(ProduceFlag.WEAK) : ProduceFlags.NONE)); } EnumSet methodConsumingConfigPhases = consumingConfigPhases.clone(); @@ -733,8 +744,14 @@ public final class ExtensionLoader { if (isRecorder && phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); } - } else if (phase == ConfigPhase.RUN_TIME) { + } else if (phase == ConfigPhase.BOOTSTRAP || phase == ConfigPhase.RUN_TIME) { if (isRecorder) { + if ((phase == ConfigPhase.BOOTSTRAP) + && !method.getReturnType().equals(RunTimeConfigurationSourceValueBuildItem.class)) { + throw reportError(parameter, + "Bootstrap configuration can only be used in a Build step that returns " + + RunTimeConfigurationSourceValueBuildItem.class.getSimpleName()); + } methodParamFns.add((bc, bri) -> { final RunTimeConfigurationProxyBuildItem proxies = bc .consume(RunTimeConfigurationProxyBuildItem.class); @@ -743,7 +760,9 @@ public final class ExtensionLoader { runTimeProxies.computeIfAbsent(parameterClass, ReflectUtil::newInstance); } else { throw reportError(parameter, - "Run time configuration cannot be consumed here unless the method is a @Recorder"); + String.format( + "%s configuration cannot be consumed here unless the method is a @Recorder", + phase == ConfigPhase.RUN_TIME ? "Run time" : "Bootstrap")); } } else { throw reportError(parameterClass, "Unknown value for ConfigPhase"); @@ -778,6 +797,12 @@ public final class ExtensionLoader { resultConsumer = Functions.discardingBiConsumer(); } else if (rawTypeExtends(returnType, BuildItem.class)) { final Class type = method.getReturnType().asSubclass(BuildItem.class); + if (type.equals(RunTimeConfigurationSourceValueBuildItem.class) + && (!isRecorder || recordAnnotation.value() != ExecutionTime.RUNTIME_INIT)) { + throw reportError(method, + "A Build step that returns " + RunTimeConfigurationSourceValueBuildItem.class.getSimpleName() + + " must also be annotated with @Record(ExecutionTime.RUNTIME_INIT)"); + } if (overridable) { if (weak) { methodStepConfig = methodStepConfig @@ -837,7 +862,8 @@ public final class ExtensionLoader { throw reportError(method, "Unsupported method return type " + returnType); } - if (methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) { + if (methodConsumingConfigPhases.contains(ConfigPhase.BOOTSTRAP) + || methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) { if (isRecorder && recordAnnotation.value() == ExecutionTime.STATIC_INIT) { throw reportError(method, "Bytecode recorder is static but an injected config object is declared as run time"); @@ -845,6 +871,11 @@ public final class ExtensionLoader { methodStepConfig = methodStepConfig .andThen(bsb -> bsb.consumes(RunTimeConfigurationProxyBuildItem.class)); } + if (methodConsumingConfigPhases.contains(ConfigPhase.BOOTSTRAP) + && methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) { + throw reportError(method, + "Bootstrap configuration cannot be used together in a build step with run time configuration"); + } if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED) || methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { methodStepConfig = methodStepConfig @@ -934,9 +965,14 @@ public final class ExtensionLoader { if (recordAnnotation.value() == ExecutionTime.STATIC_INIT) { bc.produce(new StaticBytecodeRecorderBuildItem(bri)); } else { - bc.produce(new MainBytecodeRecorderBuildItem(bri)); + Class buildItemClass = determineMainRecorderBuildItemType(method); + if (buildItemClass.equals(MainBytecodeRecorderBuildItem.class)) { + bc.produce(new MainBytecodeRecorderBuildItem(bri)); + } else { + assert buildItemClass == MainBootstrapConfigBytecodeRecorderBuildItem.class; + bc.produce(new MainBootstrapConfigBytecodeRecorderBuildItem(bri)); + } } - } } @@ -953,6 +989,12 @@ public final class ExtensionLoader { return chainConfig; } + private static Class determineMainRecorderBuildItemType(Method method) { + return method.getReturnType().equals(RunTimeConfigurationSourceValueBuildItem.class) + ? MainBootstrapConfigBytecodeRecorderBuildItem.class + : MainBytecodeRecorderBuildItem.class; + } + private static BooleanSupplier and(BooleanSupplier a, BooleanSupplier b) { return () -> a.getAsBoolean() && b.getAsBoolean(); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBootstrapConfigBytecodeRecorderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBootstrapConfigBytecodeRecorderBuildItem.java new file mode 100644 index 000000000..0fbb1b354 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBootstrapConfigBytecodeRecorderBuildItem.java @@ -0,0 +1,24 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.deployment.recording.BytecodeRecorderImpl; + +/** + * This build item will be used to write bytecode that supports the Bootstrap phase of the configuration + * That code essentially uses part of the configuration system to pass configuration data to + * recorders that then use the configuration to create new configuration sources. + * These sources are then used to create the final runtime configuration which then passed on + * to all the other runtime recorders + */ +public final class MainBootstrapConfigBytecodeRecorderBuildItem extends MultiBuildItem { + + private final BytecodeRecorderImpl bytecodeRecorder; + + public MainBootstrapConfigBytecodeRecorderBuildItem(BytecodeRecorderImpl bytecodeRecorder) { + this.bytecodeRecorder = bytecodeRecorder; + } + + public BytecodeRecorderImpl getBytecodeRecorder() { + return bytecodeRecorder; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceValueBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceValueBuildItem.java new file mode 100644 index 000000000..1a65ee9f0 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceValueBuildItem.java @@ -0,0 +1,29 @@ +package io.quarkus.deployment.builditem; + +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * This is a special build item that is intended to be used only to support bootstrap configuration in the following manner: + * + * A build step returns this build item (this is a limitation compared to other build items that can also be used with + * BuildProducer) + * containing a {@code RuntimeValue} that is obtained by calling a ({@code RUNTIME_INIT}) recorder. + * The build step can optionally use a configuration object that uses the {@code BOOTSTRAP} config phase and pass this + * configuration + * to the recorder to allow the recorder at runtime to customize its behavior + */ +public final class RunTimeConfigurationSourceValueBuildItem extends MultiBuildItem { + + private final RuntimeValue configSourcesValue; + + public RunTimeConfigurationSourceValueBuildItem(RuntimeValue configSourcesValue) { + this.configSourcesValue = configSourcesValue; + } + + public RuntimeValue getConfigSourcesValue() { + return configSourcesValue; + } +} 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 44e615391..51eee6672 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 @@ -64,11 +64,14 @@ public final class BuildTimeConfigurationReader { final ConfigPatternMap buildTimePatternMap; final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap bootstrapPatternMap; final ConfigPatternMap runTimePatternMap; final List buildTimeVisibleRoots; final List allRoots; + final boolean bootstrapRootsEmpty; + /** * Construct a new instance. * @@ -77,6 +80,7 @@ public final class BuildTimeConfigurationReader { public BuildTimeConfigurationReader(final List> configRoots) { Assert.checkNotNullParam("configRoots", configRoots); + List bootstrapRoots = new ArrayList<>(); List runTimeRoots = new ArrayList<>(); List buildTimeRunTimeRoots = new ArrayList<>(); List buildTimeRoots = new ArrayList<>(); @@ -98,12 +102,15 @@ public final class BuildTimeConfigurationReader { buildTimeRoots.add(definition); } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { buildTimeRunTimeRoots.add(definition); + } else if (phase == ConfigPhase.BOOTSTRAP) { + bootstrapRoots.add(definition); } else { assert phase == ConfigPhase.RUN_TIME; runTimeRoots.add(definition); } } + bootstrapPatternMap = PatternMapBuilder.makePatterns(bootstrapRoots); runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots); buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots); buildTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRoots); @@ -112,8 +119,12 @@ public final class BuildTimeConfigurationReader { buildTimeVisibleRoots.addAll(buildTimeRoots); buildTimeVisibleRoots.addAll(buildTimeRunTimeRoots); - List allRoots = new ArrayList<>(buildTimeVisibleRoots.size() + runTimeRoots.size()); + bootstrapRootsEmpty = bootstrapRoots.isEmpty(); + + List allRoots = new ArrayList<>( + buildTimeVisibleRoots.size() + bootstrapRoots.size() + runTimeRoots.size()); allRoots.addAll(buildTimeVisibleRoots); + allRoots.addAll(bootstrapRoots); allRoots.addAll(runTimeRoots); this.allRoots = allRoots; @@ -332,6 +343,21 @@ public final class BuildTimeConfigurationReader { ni.goToStart(); ni.next(); matched = runTimePatternMap.match(ni); + if (matched != null) { + // it's a specified run-time default (record for later) + boolean old = ExpandingConfigSource.setExpanding(false); + try { + specifiedRunTimeDefaultValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + } finally { + ExpandingConfigSource.setExpanding(old); + } + continue; + } + // also check for the bootstrap properties since those need to be added to specifiedRunTimeDefaultValues as well + ni.goToStart(); + ni.next(); + matched = bootstrapPatternMap.match(ni); if (matched != null) { // it's a specified run-time default (record for later) boolean old = ExpandingConfigSource.setExpanding(false); @@ -354,7 +380,8 @@ public final class BuildTimeConfigurationReader { } } return new ReadResult(objectsByRootClass, specifiedRunTimeDefaultValues, buildTimeRunTimeVisibleValues, - buildTimePatternMap, buildTimeRunTimePatternMap, runTimePatternMap, allRoots); + buildTimePatternMap, buildTimeRunTimePatternMap, bootstrapPatternMap, runTimePatternMap, allRoots, + bootstrapRootsEmpty); } /** @@ -689,22 +716,28 @@ public final class BuildTimeConfigurationReader { final Map buildTimeRunTimeVisibleValues; final ConfigPatternMap buildTimePatternMap; final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap bootstrapPatternMap; final ConfigPatternMap runTimePatternMap; final Map, RootDefinition> runTimeRootsByClass; final List allRoots; + final boolean bootstrapRootsEmpty; ReadResult(final Map, Object> objectsByRootClass, final Map specifiedRunTimeDefaultValues, final Map buildTimeRunTimeVisibleValues, final ConfigPatternMap buildTimePatternMap, final ConfigPatternMap buildTimeRunTimePatternMap, - final ConfigPatternMap runTimePatternMap, final List allRoots) { + final ConfigPatternMap bootstrapPatternMap, + final ConfigPatternMap runTimePatternMap, final List allRoots, + boolean bootstrapRootsEmpty) { this.objectsByRootClass = objectsByRootClass; this.specifiedRunTimeDefaultValues = specifiedRunTimeDefaultValues; this.buildTimeRunTimeVisibleValues = buildTimeRunTimeVisibleValues; this.buildTimePatternMap = buildTimePatternMap; this.buildTimeRunTimePatternMap = buildTimeRunTimePatternMap; + this.bootstrapPatternMap = bootstrapPatternMap; this.runTimePatternMap = runTimePatternMap; this.allRoots = allRoots; + this.bootstrapRootsEmpty = bootstrapRootsEmpty; Map, RootDefinition> map = new HashMap<>(); for (RootDefinition root : allRoots) { map.put(root.getConfigurationClass(), root); @@ -740,6 +773,10 @@ public final class BuildTimeConfigurationReader { return buildTimeRunTimePatternMap; } + public ConfigPatternMap getBootstrapPatternMap() { + return bootstrapPatternMap; + } + public ConfigPatternMap getRunTimePatternMap() { return runTimePatternMap; } @@ -748,6 +785,10 @@ public final class BuildTimeConfigurationReader { return allRoots; } + public boolean isBootstrapRootsEmpty() { + return bootstrapRootsEmpty; + } + public RootDefinition requireRootDefinitionForClass(Class clazz) { final RootDefinition def = runTimeRootsByClass.get(clazz); if (def == null) { 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 40486e403..37e7328b6 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 @@ -3,6 +3,7 @@ package io.quarkus.deployment.configuration; import static io.quarkus.deployment.util.ReflectUtil.reportError; import java.lang.reflect.Field; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -73,6 +74,7 @@ import io.smallrye.config.SmallRyeConfigBuilder; 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"; @@ -84,13 +86,18 @@ public final class RunTimeConfigurationGenerator { 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_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, - "createRunTimeConfig", void.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, "ensureInitialized", void.class); + static final FieldDescriptor C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "bootstrapDefaultsConfigSource", ConfigSource.class); static final FieldDescriptor C_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "runTimeDefaultsConfigSource", ConfigSource.class); - static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class); + static final MethodDescriptor C_BOOTSTRAP_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readBootstrapConfig", + void.class); + public static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class, + List.class); static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "specifiedRunTimeConfigSource", ConfigSource.class); @@ -138,8 +145,13 @@ public final class RunTimeConfigurationGenerator { IntFunction.class); static final MethodDescriptor CU_CONFIG_BUILDER = MethodDescriptor.ofMethod(ConfigUtils.class, "configBuilder", SmallRyeConfigBuilder.class, boolean.class); + static final MethodDescriptor CU_CONFIG_BUILDER_WITH_ADD_DISCOVERED = MethodDescriptor.ofMethod(ConfigUtils.class, + "configBuilder", + SmallRyeConfigBuilder.class, boolean.class, boolean.class); static final MethodDescriptor CU_ADD_SOURCE_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceProvider", void.class, SmallRyeConfigBuilder.class, ConfigSourceProvider.class); + static final MethodDescriptor CU_ADD_SOURCE_PROVIDERS = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceProviders", + void.class, SmallRyeConfigBuilder.class, Collection.class); static final MethodDescriptor HM_NEW = MethodDescriptor.ofConstructor(HashMap.class); static final MethodDescriptor HM_PUT = MethodDescriptor.ofMethod(HashMap.class, "put", Object.class, Object.class, @@ -195,6 +207,7 @@ public final class RunTimeConfigurationGenerator { static final MethodDescriptor QCF_SET_CONFIG = MethodDescriptor.ofMethod(QuarkusConfigFactory.class, "setConfig", void.class, SmallRyeConfig.class); + static final MethodDescriptor BSDVCS_NEW = MethodDescriptor.ofConstructor(BSDVCS_CLASS_NAME); static final MethodDescriptor RTDVCS_NEW = MethodDescriptor.ofConstructor(RTDVCS_CLASS_NAME); static final MethodDescriptor SRC_GET_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getConverter", @@ -229,11 +242,12 @@ public final class RunTimeConfigurationGenerator { final ClassCreator cc; final MethodCreator clinit; final BytecodeCreator converterSetup; + final MethodCreator readBootstrapConfig; + final ResultHandle readBootstrapConfigNameBuilder; final MethodCreator readConfig; final ResultHandle readConfigNameBuilder; final ResultHandle clinitNameBuilder; final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult; - final ConfigPatternMap runTimePatternMap; final List roots; // default values given in the build configuration final Map specifiedRunTimeDefaultValues; @@ -309,6 +323,13 @@ public final class RunTimeConfigurationGenerator { 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) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(BSDVCS_NEW)); + } + // the run time default values config source cc.getFieldCreator(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE) .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); @@ -327,29 +348,37 @@ public final class RunTimeConfigurationGenerator { // block for converter setup converterSetup = clinit.createScope(); + + // create readBootstrapConfig method - this will always exist whether or not it contains a method body + // the method body will be empty when there are no bootstrap configuration roots + readBootstrapConfig = cc.getMethodCreator(C_BOOTSTRAP_CONFIG); + if (buildTimeReadResult.isBootstrapRootsEmpty()) { + readBootstrapConfigNameBuilder = null; + } else { + readBootstrapConfigNameBuilder = readBootstrapConfig.newInstance(SB_NEW); + readBootstrapConfig.invokeVirtualMethod(SB_APPEND_STRING, readBootstrapConfigNameBuilder, + readBootstrapConfig.load("quarkus")); + } + // create readConfig readConfig = cc.getMethodCreator(C_READ_CONFIG); // the readConfig name builder readConfigNameBuilder = readConfig.newInstance(SB_NEW); readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus")); - runTimePatternMap = buildTimeReadResult.getRunTimePatternMap(); + accessorFinder = new AccessorFinder(); } + // meant to be called in outside the constructor + private boolean bootstrapConfigSetupNeeded() { + return readBootstrapConfigNameBuilder != null; + } + public void run() { // in clinit, load the build-time config // make the build time config global until we read the run time config - // at run time (when we're ready) we update the factory and then release the build time config - clinit.invokeStaticMethod(QCF_SET_CONFIG, clinitConfig); - // release any previous configuration - final ResultHandle clinitCpr = clinit.invokeStaticMethod(CPR_INSTANCE); - try (TryBlock getConfigTry = clinit.tryBlock()) { - final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, - clinitCpr); - getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, clinitCpr, initialConfigHandle); - // ignore - getConfigTry.addCatch(IllegalStateException.class); - } + installConfiguration(clinitConfig, clinit); // fill roots map for (RootDefinition root : roots) { @@ -361,19 +390,29 @@ public final class RunTimeConfigurationGenerator { final ConfigPatternMap buildTimePatternMap = buildTimeConfigResult.getBuildTimePatternMap(); final ConfigPatternMap buildTimeRunTimePatternMap = buildTimeConfigResult .getBuildTimeRunTimePatternMap(); + final ConfigPatternMap bootstrapPatternMap = buildTimeConfigResult.getBootstrapPatternMap(); final ConfigPatternMap runTimePatternMap = buildTimeConfigResult.getRunTimePatternMap(); final BiFunction combinator = (a, b) -> a == null ? b : a; - final ConfigPatternMap buildTimeRunTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, - runTimePatternMap, combinator); - final ConfigPatternMap runTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, - buildTimeRunTimePatternMap, combinator); + final ConfigPatternMap buildTimeRunTimeIgnored = ConfigPatternMap + .merge(ConfigPatternMap.merge(buildTimePatternMap, + runTimePatternMap, combinator), bootstrapPatternMap, combinator); + final ConfigPatternMap runTimeIgnored = ConfigPatternMap + .merge(ConfigPatternMap.merge(buildTimePatternMap, + buildTimeRunTimePatternMap, combinator), bootstrapPatternMap, combinator); final MethodDescriptor siParserBody = generateParserBody(buildTimeRunTimePatternMap, buildTimeRunTimeIgnored, new StringBuilder("siParseKey"), false, false); final MethodDescriptor rtParserBody = generateParserBody(runTimePatternMap, runTimeIgnored, new StringBuilder("rtParseKey"), false, true); + // create the bootstrap config if necessary + ResultHandle bootstrapBuilder = null; + if (bootstrapConfigSetupNeeded()) { + bootstrapBuilder = readBootstrapConfig.invokeStaticMethod(CU_CONFIG_BUILDER_WITH_ADD_DISCOVERED, + readBootstrapConfig.load(false), readBootstrapConfig.load(false)); + } + // create the run time config final ResultHandle runTimeBuilder = readConfig.invokeStaticMethod(CU_CONFIG_BUILDER, readConfig.load(true)); @@ -399,16 +438,43 @@ public final class RunTimeConfigurationGenerator { cc.getFieldCreator(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); clinit.writeStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE, specifiedRunTimeSource); + // 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)); + // specified run time config default values + readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 1, + 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.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE)); + } + // add in our custom sources - final ResultHandle array = readConfig.newArray(ConfigSource[].class, 4); + final ResultHandle runtimeConfigSourcesArray = readConfig.newArray(ConfigSource[].class, + bootstrapConfigSetupNeeded() ? 5 : 4); // build time config (expanded values) - readConfig.writeArrayValue(array, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); + readConfig.writeArrayValue(runtimeConfigSourcesArray, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); // specified run time config default values - readConfig.writeArrayValue(array, 1, readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); + readConfig.writeArrayValue(runtimeConfigSourcesArray, 1, + readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); // run time config default values - readConfig.writeArrayValue(array, 2, readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); + readConfig.writeArrayValue(runtimeConfigSourcesArray, 2, + readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); // build time run time visible default config source - readConfig.writeArrayValue(array, 3, readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_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.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE)); + } // add in known converters for (Class additionalType : additionalTypes) { @@ -430,28 +496,41 @@ public final class RunTimeConfigurationGenerator { for (Map.Entry> entry : convertersToRegister.entrySet()) { final FieldDescriptor descriptor = entry.getKey(); final Class type = entry.getValue(); + if (bootstrapConfigSetupNeeded()) { + readBootstrapConfig.invokeVirtualMethod(SRCB_WITH_CONVERTER, bootstrapBuilder, + readBootstrapConfig.loadClass(type), + readBootstrapConfig.load(100), readBootstrapConfig.readStaticField(descriptor)); + } readConfig.invokeVirtualMethod(SRCB_WITH_CONVERTER, runTimeBuilder, readConfig.loadClass(type), readConfig.load(100), readConfig.readStaticField(descriptor)); } } - // put them in the builder - readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, array); + // put sources in the bootstrap builder + if (bootstrapConfigSetupNeeded()) { + readBootstrapConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, bootstrapBuilder, bootstrapConfigSourcesArray); + } + // put sources in the builder + readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, runtimeConfigSourcesArray); - final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder); - // install run time config - readConfig.invokeStaticMethod(QCF_SET_CONFIG, runTimeConfig); - // now invalidate the cached config, so the next one to load the config gets the new one - final ResultHandle configProviderResolver = readConfig.invokeStaticMethod(CPR_INSTANCE); - try (TryBlock getConfigTry = readConfig.tryBlock()) { - final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, - configProviderResolver); - getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle); - // ignore - getConfigTry.addCatch(IllegalStateException.class); + // add the ConfigSourceProvider List passed as the readConfig method param + // (which were generated by the bootstrap config phase - an empty list is passed when there is no bootstrap phase) + readConfig.invokeStaticMethod(CU_ADD_SOURCE_PROVIDERS, runTimeBuilder, readConfig.getMethodParam(0)); + + ResultHandle bootstrapConfig = null; + if (bootstrapConfigSetupNeeded()) { + bootstrapConfig = readBootstrapConfig.invokeVirtualMethod(SRCB_BUILD, bootstrapBuilder); + installConfiguration(bootstrapConfig, readBootstrapConfig); } + final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder); + installConfiguration(runTimeConfig, readConfig); + final ResultHandle clInitOldLen = clinit.invokeVirtualMethod(SB_LENGTH, clinitNameBuilder); + ResultHandle bcOldLen = null; + if (bootstrapConfigSetupNeeded()) { + bcOldLen = readBootstrapConfig.invokeVirtualMethod(SB_LENGTH, readBootstrapConfigNameBuilder); + } final ResultHandle rcOldLen = readConfig.invokeVirtualMethod(SB_LENGTH, readConfigNameBuilder); // generate eager config read (both build and run time at once) @@ -487,6 +566,25 @@ public final class RunTimeConfigurationGenerator { } clinit.invokeStaticMethod(initGroup, clinitConfig, clinitNameBuilder, instance); clinit.invokeVirtualMethod(SB_SET_LENGTH, clinitNameBuilder, clInitOldLen); + } else if (root.getConfigPhase() == ConfigPhase.BOOTSTRAP) { + if (bootstrapConfigSetupNeeded()) { + // config root field is volatile; we initialize and read config from the readBootstrapConfig method + cc.getFieldCreator(rootFieldDescriptor) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); + // construct instance in readBootstrapConfig + final ResultHandle instance = readBootstrapConfig.invokeStaticMethod(ctor); + // assign instance to field + readBootstrapConfig.writeStaticField(rootFieldDescriptor, instance); + if (!rootName.isEmpty()) { + readBootstrapConfig.invokeVirtualMethod(SB_APPEND_CHAR, readBootstrapConfigNameBuilder, + readBootstrapConfig.load('.')); + readBootstrapConfig.invokeVirtualMethod(SB_APPEND_STRING, readBootstrapConfigNameBuilder, + readBootstrapConfig.load(rootName)); + } + readBootstrapConfig.invokeStaticMethod(initGroup, bootstrapConfig, readBootstrapConfigNameBuilder, + instance); + readBootstrapConfig.invokeVirtualMethod(SB_SET_LENGTH, readBootstrapConfigNameBuilder, bcOldLen); + } } else if (root.getConfigPhase() == ConfigPhase.RUN_TIME) { // config root field is volatile; we initialize and read config from the readConfig method cc.getFieldCreator(rootFieldDescriptor) @@ -569,12 +667,12 @@ public final class RunTimeConfigurationGenerator { mc.returnValue(null); } - // generate run time entry point - try (MethodCreator mc = cc.getMethodCreator(C_CREATE_RUN_TIME_CONFIG)) { + // generate bootstrap config entry point + try (MethodCreator mc = cc.getMethodCreator(C_CREATE_BOOTSTRAP_CONFIG)) { mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); ResultHandle instance = mc.newInstance(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME)); - mc.invokeVirtualMethod(C_READ_CONFIG, instance); - mc.returnValue(null); + mc.invokeVirtualMethod(C_BOOTSTRAP_CONFIG, instance); + mc.returnValue(instance); } // wrap it up @@ -600,6 +698,9 @@ public final class RunTimeConfigurationGenerator { configurationException, emptyStackTraceElement); isError.throwException(configurationException); + readBootstrapConfig.returnValue(null); + readBootstrapConfig.close(); + readConfig.returnValue(null); readConfig.close(); @@ -607,40 +708,39 @@ public final class RunTimeConfigurationGenerator { clinit.close(); cc.close(); - // generate run time default values config source class - try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(RTDVCS_CLASS_NAME) - .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { - // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) - try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { - final ResultHandle keyIter = mc.getMethodParam(0); - final MethodDescriptor md = generateDefaultValueParse(dvcc, runTimePatternMap, - new StringBuilder("getDefaultFor")); - if (md != null) { - // there is at least one default value - final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter)); - try (BytecodeCreator true1 = if1.trueBranch()) { - true1.invokeVirtualMethod(NI_NEXT, keyIter); - final BranchResult if2 = true1 - .ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus"))); - try (BytecodeCreator true2 = if2.trueBranch()) { - final ResultHandle result = true2.invokeVirtualMethod( - md, mc.getThis(), keyIter); - true2.returnValue(result); - } - } - } - - mc.returnValue(mc.loadNull()); - } + if (bootstrapConfigSetupNeeded()) { + // generate bootstrap default values config source class + generateDefaultValuesConfigSourceClass(bootstrapPatternMap, BSDVCS_CLASS_NAME); } + // generate run time default values config source class + generateDefaultValuesConfigSourceClass(runTimePatternMap, RTDVCS_CLASS_NAME); + // generate build time run time visible default values config source class - try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(BTRTDVCS_CLASS_NAME) + generateDefaultValuesConfigSourceClass(buildTimeRunTimePatternMap, BTRTDVCS_CLASS_NAME); + } + + private void installConfiguration(ResultHandle config, MethodCreator methodCreator) { + // install config + methodCreator.invokeStaticMethod(QCF_SET_CONFIG, config); + // now invalidate the cached config, so the next one to load the config gets the new one + final ResultHandle configProviderResolver = methodCreator.invokeStaticMethod(CPR_INSTANCE); + try (TryBlock getConfigTry = methodCreator.tryBlock()) { + final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, + configProviderResolver); + getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle); + // ignore + getConfigTry.addCatch(IllegalStateException.class); + } + } + + private void generateDefaultValuesConfigSourceClass(ConfigPatternMap patternMap, String className) { + try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(className) .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { final ResultHandle keyIter = mc.getMethodParam(0); - final MethodDescriptor md = generateDefaultValueParse(dvcc, buildTimeRunTimePatternMap, + final MethodDescriptor md = generateDefaultValueParse(dvcc, patternMap, new StringBuilder("getDefaultFor")); if (md != null) { // there is at least one default value diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java index 3fc15bf99..ffbf3c46d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java @@ -40,6 +40,16 @@ public final class RootDefinition extends ClassDefinition { "Run", "Time", "Config"), "Configuration"), "Config"); + } else if (configPhase == ConfigPhase.BOOTSTRAP) { + trimmedSegments = withoutSuffix( + withoutSuffix( + withoutSuffix( + withoutSuffix( + segments, + "Bootstrap", "Configuration"), + "Bootstrap", "Config"), + "Configuration"), + "Config"); } else { trimmedSegments = withoutSuffix( withoutSuffix( diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java index c2f72b3a8..0c9378250 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java @@ -34,6 +34,7 @@ public class ConfigDescriptionBuildStep { List ret = new ArrayList<>(); processConfig(config.getReadResult().getBuildTimePatternMap(), ret, javadoc); processConfig(config.getReadResult().getBuildTimeRunTimePatternMap(), ret, javadoc); + processConfig(config.getReadResult().getBootstrapPatternMap(), ret, javadoc); processConfig(config.getReadResult().getRunTimePatternMap(), ret, javadoc); return ret; } 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 849ebd9e8..895809665 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 @@ -5,12 +5,15 @@ import static io.quarkus.gizmo.MethodDescriptor.ofMethod; import java.io.File; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.graalvm.nativeimage.ImageInfo; import org.jboss.logging.Logger; @@ -27,6 +30,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.MainBootstrapConfigBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; @@ -50,6 +54,7 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.runtime.Application; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.StartupContext; import io.quarkus.runtime.StartupTask; import io.quarkus.runtime.Timing; @@ -68,6 +73,7 @@ class MainClassBuildStep { MainClassBuildItem build(List staticInitTasks, List substitutions, List mainMethod, + List mainBootstrapConfig, List properties, List javaLibraryPathAdditionalPaths, Optional sslTrustStoreSystemProperty, @@ -139,19 +145,7 @@ class MainClassBuildStep { for (StaticBytecodeRecorderBuildItem holder : staticInitTasks) { final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); if (!recorder.isEmpty()) { - // Register substitutions in all recorders - for (ObjectSubstitutionBuildItem sub : substitutions) { - ObjectSubstitutionBuildItem.Holder holder1 = sub.holder; - recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution); - } - for (BytecodeRecorderObjectLoaderBuildItem item : loaders) { - recorder.registerObjectLoader(item.getObjectLoader()); - } - recorder.writeBytecode(gizmoOutput); - - ResultHandle dup = tryBlock.newInstance(ofConstructor(recorder.getClassName())); - tryBlock.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup, - startupContext); + writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock); } } tryBlock.returnValue(null); @@ -213,24 +207,61 @@ class MainClassBuildStep { tryBlock = mv.tryBlock(); - // Load the run time configuration - tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG); + // Load the bootstrap configuration + ResultHandle generatedConfig = tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG); + + if (mainBootstrapConfig.isEmpty()) { + tryBlock.invokeVirtualMethod(RunTimeConfigurationGenerator.C_READ_CONFIG, generatedConfig, + tryBlock.invokeStaticMethod(ofMethod(Collections.class, "emptyList", List.class))); + } else { + /* + * This is not that great, since it is no by no means a general way of executing things before the main bytecode + * stuff executes. + * It's tailored made to support the bootstrap configuration stuff and would need to be rethought if we need + * a general mechanism of executing things before the main bytecode stuff + * (which would likely involve a new runtime phase) + * + * What this loop does is go through the MainBootstrapConfigBytecodeRecorderBuildItem objects which are guaranteed + * to be constructed from build steps that return RunTimeConfigurationSourceValueBuildItem - thus ensuring that + * the generated StartupTask will write its result (which is a RuntimeValue + * configSourcesValue) into the StartupContext. + * This value is then pulled out of the StartupContext by using the getLastValue method. All the + * ConfigSourceProvider objects are then collected and passed to Config.readConfig() + */ + ResultHandle configSourcesProvidersList = tryBlock.newInstance(ofConstructor(ArrayList.class, int.class), + tryBlock.load(mainBootstrapConfig.size())); + for (MainBootstrapConfigBytecodeRecorderBuildItem holder : mainBootstrapConfig) { + final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); + if (!recorder.isEmpty()) { + writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock); + + // we now get the result of the recorder invocation + ResultHandle isLastValueSet = tryBlock.invokeVirtualMethod( + ofMethod(StartupContext.class, "isLastValueSet", boolean.class), startupContext); + BytecodeCreator isLastValueSetTrue = tryBlock.ifNonZero(isLastValueSet).trueBranch(); + ResultHandle getLastValue = isLastValueSetTrue + .invokeVirtualMethod(ofMethod(StartupContext.class, "getLastValue", Object.class), startupContext); + BytecodeCreator lastValueNotNull = isLastValueSetTrue.ifNull(getLastValue).falseBranch(); + ResultHandle runtimeValue = lastValueNotNull.checkCast(getLastValue, RuntimeValue.class); + ResultHandle getValue = lastValueNotNull + .invokeVirtualMethod(MethodDescriptor.ofMethod(RuntimeValue.class, "getValue", Object.class), + runtimeValue); + ResultHandle configSourceProvider = lastValueNotNull.checkCast(getValue, ConfigSourceProvider.class); + lastValueNotNull.invokeVirtualMethod( + MethodDescriptor.ofMethod(ArrayList.class, "add", boolean.class, Object.class), + configSourcesProvidersList, configSourceProvider); + lastValueNotNull.breakScope(); + isLastValueSetTrue.breakScope(); + } + } + tryBlock.invokeVirtualMethod(RunTimeConfigurationGenerator.C_READ_CONFIG, generatedConfig, + configSourcesProvidersList); + } for (MainBytecodeRecorderBuildItem holder : mainMethod) { final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); if (!recorder.isEmpty()) { - // Register substitutions in all recorders - for (ObjectSubstitutionBuildItem sub : substitutions) { - ObjectSubstitutionBuildItem.Holder holder1 = sub.holder; - recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution); - } - for (BytecodeRecorderObjectLoaderBuildItem item : loaders) { - recorder.registerObjectLoader(item.getObjectLoader()); - } - recorder.writeBytecode(gizmoOutput); - ResultHandle dup = tryBlock.newInstance(ofConstructor(recorder.getClassName())); - tryBlock.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup, - startupContext); + writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock); } } @@ -294,4 +325,20 @@ class MainClassBuildStep { return new MainClassBuildItem(MAIN_CLASS); } + private void writeRecordedBytecode(BytecodeRecorderImpl recorder, List substitutions, + List loaders, GeneratedClassGizmoAdaptor gizmoOutput, + ResultHandle startupContext, BytecodeCreator bytecodeCreator) { + for (ObjectSubstitutionBuildItem sub : substitutions) { + ObjectSubstitutionBuildItem.Holder holder1 = sub.holder; + recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution); + } + for (BytecodeRecorderObjectLoaderBuildItem item : loaders) { + recorder.registerObjectLoader(item.getObjectLoader()); + } + recorder.writeBytecode(gizmoOutput); + ResultHandle dup = bytecodeCreator.newInstance(ofConstructor(recorder.getClassName())); + bytecodeCreator.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup, + startupContext); + } + } diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java index bd6f18398..4a249293d 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java @@ -82,7 +82,7 @@ public class StartupActionImpl implements StartupAction { try { final Class configClass = Class.forName(RunTimeConfigurationGenerator.CONFIG_CLASS_NAME, true, runtimeClassLoader); - configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG.getName()) + configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG.getName()) .invoke(null); } catch (Throwable t2) { t.addSuppressed(t2); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index d5025d226..f615d4b34 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -74,6 +74,7 @@ final public class Constants { Constants.MEMORY_SIZE_NOTE_ANCHOR, "MemorySize"); public static final String CONFIG_PHASE_RUNTIME_ILLUSTRATION = "icon:cogs[title=Overridable at runtime]"; + public static final String CONFIG_PHASE_BOOTSTRAP_ILLUSTRATION = "icon:cogs[title=Bootstrap - Overridable at runtime]"; public static final String CONFIG_PHASE_BUILD_TIME_ILLUSTRATION = "icon:archive[title=Fixed at build time]"; public static final String CONFIG_PHASE_LEGEND = String.format( "%n%s Configuration property fixed at build time - %s️ Configuration property overridable at runtime %n", diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java index 55e72faab..c5aa797fb 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java @@ -6,6 +6,8 @@ import io.quarkus.annotation.processor.Constants; public enum ConfigPhase implements Comparable { RUN_TIME("The configuration is overridable at runtime", Constants.CONFIG_PHASE_RUNTIME_ILLUSTRATION, "RunTime"), + BOOTSTRAP("The configuration is used to bootstrap runtime Config Sources and is overridable at runtime", + Constants.CONFIG_PHASE_BOOTSTRAP_ILLUSTRATION, "Bootstrap"), BUILD_TIME("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION, "BuildTime"), BUILD_AND_RUN_TIME_FIXED("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION, "BuildTime"); @@ -37,6 +39,20 @@ public enum ConfigPhase implements Comparable { return -1; } } + case BOOTSTRAP: { + switch (secondPhase) { + case BUILD_TIME: + return 1; + case BUILD_AND_RUN_TIME_FIXED: + return 1; + case BOOTSTRAP: + return 0; + case RUN_TIME: + return 1; + default: + return 1; + } + } case RUN_TIME: { switch (secondPhase) { case RUN_TIME: diff --git a/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java b/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java index efbc097df..be10ea5c5 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/StartupContext.java @@ -14,6 +14,9 @@ public class StartupContext implements Closeable { private static final Logger LOG = Logger.getLogger(StartupContext.class); private final Map values = new HashMap<>(); + private Object lastValue; + // this is done to distinguish between the value never having been set and having been set as null + private boolean lastValueSet = false; private final List shutdownTasks = new ArrayList<>(); private final List lastShutdownTasks = new ArrayList<>(); private final ShutdownContext shutdownContext = new ShutdownContext() { @@ -34,12 +37,22 @@ public class StartupContext implements Closeable { public void putValue(String name, Object value) { values.put(name, value); + lastValueSet = true; + this.lastValue = value; } public Object getValue(String name) { return values.get(name); } + public Object getLastValue() { + return lastValue; + } + + public boolean isLastValueSet() { + return lastValueSet; + } + @Override public void close() { runAllInReverseOrder(shutdownTasks); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java index 27278258e..cec012b52 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java @@ -9,6 +9,13 @@ public enum ConfigPhase { * Values are read and available for usage at build time, and available on a read-only basis at run time. */ BUILD_AND_RUN_TIME_FIXED(true, true, false), + + /** + * Values are read and available for usage at run time and are re-read on each program execution. These values + * are used to configure ConfigSourceProvider implementations + */ + BOOTSTRAP(false, true, true), + /** * Values are read and available for usage at run time and are re-read on each program execution. */ 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 774359653..f56938c66 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 @@ -10,6 +10,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -52,13 +53,18 @@ public final class ConfigUtils { return size -> new TreeSet<>(); } + public static SmallRyeConfigBuilder configBuilder(final boolean runTime) { + return configBuilder(runTime, true); + } + /** * Get the basic configuration builder. * * @param runTime {@code true} if the configuration is run time, {@code false} if build time + * @param addDiscovered {@code true} if the ConfigSource and Converter objects should be auto-discovered * @return the configuration builder */ - public static SmallRyeConfigBuilder configBuilder(final boolean runTime) { + public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final boolean addDiscovered) { final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); final ApplicationPropertiesConfigSource.InFileSystem inFileSystem = new ApplicationPropertiesConfigSource.InFileSystem(); final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); @@ -83,8 +89,10 @@ public final class ConfigUtils { sources.add(new SysPropConfigSource()); builder.withSources(sources.toArray(new ConfigSource[0])); } - builder.addDiscoveredSources(); - builder.addDiscoveredConverters(); + if (addDiscovered) { + builder.addDiscoveredSources(); + builder.addDiscoveredConverters(); + } return builder; } @@ -101,6 +109,18 @@ public final class ConfigUtils { } } + /** + * Add a configuration source providers to the builder. + * + * @param builder the builder + * @param providers the providers to add + */ + public static void addSourceProviders(SmallRyeConfigBuilder builder, Collection providers) { + for (ConfigSourceProvider provider : providers) { + addSourceProvider(builder, provider); + } + } + static class EnvConfigSource implements ConfigSource { static final Pattern REP_PATTERN = Pattern.compile("[^a-zA-Z0-9_]"); diff --git a/integration-tests/bootstrap-config/application/pom.xml b/integration-tests/bootstrap-config/application/pom.xml new file mode 100644 index 000000000..47c2ba5f4 --- /dev/null +++ b/integration-tests/bootstrap-config/application/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-test-bootstrap-config + 999-SNAPSHOT + ../ + + + quarkus-integration-test-bootstrap-config-application + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-integration-test-bootstrap-config-extension + 999-SNAPSHOT + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + native + + + + \ No newline at end of file diff --git a/integration-tests/bootstrap-config/application/src/main/java/io/quarkus/it/bootstrap/config/GreetingResource.java b/integration-tests/bootstrap-config/application/src/main/java/io/quarkus/it/bootstrap/config/GreetingResource.java new file mode 100644 index 000000000..fb84b4105 --- /dev/null +++ b/integration-tests/bootstrap-config/application/src/main/java/io/quarkus/it/bootstrap/config/GreetingResource.java @@ -0,0 +1,18 @@ +package io.quarkus.it.bootstrap.config; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Path("/greeting") +public class GreetingResource { + + @ConfigProperty(name = "foo.key.i2") + String propertyFromBootstrapConfig; + + @GET + public String greet() { + return "hello " + propertyFromBootstrapConfig; + } +} diff --git a/integration-tests/bootstrap-config/application/src/main/resources/application.properties b/integration-tests/bootstrap-config/application/src/main/resources/application.properties new file mode 100644 index 000000000..f79815b0c --- /dev/null +++ b/integration-tests/bootstrap-config/application/src/main/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.dummy.name=foo +quarkus.application.name=dummy \ No newline at end of file diff --git a/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTest.java b/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTest.java new file mode 100644 index 000000000..7fc45b1ff --- /dev/null +++ b/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTest.java @@ -0,0 +1,23 @@ +package io.quarkus.it.bootstrap.config; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class GreetingResourceTest { + + @Test + void testEndpoint() { + given() + .when().get("/greeting") + .then() + .statusCode(200) + .body(containsString("hello")) + .body(containsString("dummy2")); + } + +} diff --git a/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTestIT.java b/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTestIT.java new file mode 100644 index 000000000..f4f17d220 --- /dev/null +++ b/integration-tests/bootstrap-config/application/src/test/java/io/quarkus/it/bootstrap/config/GreetingResourceTestIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.bootstrap.config; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class GreetingResourceTestIT extends GreetingResourceTest { +} \ No newline at end of file diff --git a/integration-tests/bootstrap-config/extension/deployment/pom.xml b/integration-tests/bootstrap-config/extension/deployment/pom.xml new file mode 100644 index 000000000..351e97508 --- /dev/null +++ b/integration-tests/bootstrap-config/extension/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-test-bootstrap-config-extension-parent + 999-SNAPSHOT + ../ + + + quarkus-integration-test-bootstrap-config-extension-deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-integration-test-bootstrap-config-extension + 999-SNAPSHOT + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + \ No newline at end of file diff --git a/integration-tests/bootstrap-config/extension/deployment/src/main/java/io/quarkus/it/bootstrap/config/extension/deployment/DummyBootstrapConfigBuildStep.java b/integration-tests/bootstrap-config/extension/deployment/src/main/java/io/quarkus/it/bootstrap/config/extension/deployment/DummyBootstrapConfigBuildStep.java new file mode 100644 index 000000000..4c83e403c --- /dev/null +++ b/integration-tests/bootstrap-config/extension/deployment/src/main/java/io/quarkus/it/bootstrap/config/extension/deployment/DummyBootstrapConfigBuildStep.java @@ -0,0 +1,26 @@ +package io.quarkus.it.bootstrap.config.extension.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem; +import io.quarkus.it.bootstrap.config.extension.DummyBootstrapRecorder; +import io.quarkus.it.bootstrap.config.extension.DummyBootstrapRecorder2; +import io.quarkus.it.bootstrap.config.extension.DummyConfig; +import io.quarkus.runtime.ApplicationConfig; + +public class DummyBootstrapConfigBuildStep { + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public RunTimeConfigurationSourceValueBuildItem dummyRecorder(DummyBootstrapRecorder recorder, DummyConfig dummyConfig, + ApplicationConfig applicationConfig) { + return new RunTimeConfigurationSourceValueBuildItem(recorder.create(dummyConfig, applicationConfig)); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public RunTimeConfigurationSourceValueBuildItem dummyRecorder2(DummyBootstrapRecorder2 recorder) { + return new RunTimeConfigurationSourceValueBuildItem(recorder.create()); + } +} diff --git a/integration-tests/bootstrap-config/extension/pom.xml b/integration-tests/bootstrap-config/extension/pom.xml new file mode 100644 index 000000000..bcfa99cb4 --- /dev/null +++ b/integration-tests/bootstrap-config/extension/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + + io.quarkus + quarkus-build-parent + 999-SNAPSHOT + ../../../build-parent/pom.xml + + + quarkus-integration-test-bootstrap-config-extension-parent + pom + + + deployment + runtime + + + + \ No newline at end of file diff --git a/integration-tests/bootstrap-config/extension/runtime/pom.xml b/integration-tests/bootstrap-config/extension/runtime/pom.xml new file mode 100644 index 000000000..c9417117f --- /dev/null +++ b/integration-tests/bootstrap-config/extension/runtime/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-test-bootstrap-config-extension-parent + 999-SNAPSHOT + ../ + + + quarkus-integration-test-bootstrap-config-extension + + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + \ No newline at end of file diff --git a/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder.java b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder.java new file mode 100644 index 000000000..76b582575 --- /dev/null +++ b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder.java @@ -0,0 +1,15 @@ +package io.quarkus.it.bootstrap.config.extension; + +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.runtime.ApplicationConfig; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class DummyBootstrapRecorder { + + public RuntimeValue create(DummyConfig dummyConfig, ApplicationConfig applicationConfig) { + return new RuntimeValue<>(new DummyConfigSourceProvider(dummyConfig, applicationConfig)); + } +} diff --git a/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder2.java b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder2.java new file mode 100644 index 000000000..80424f00b --- /dev/null +++ b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyBootstrapRecorder2.java @@ -0,0 +1,22 @@ +package io.quarkus.it.bootstrap.config.extension; + +import java.util.Collections; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class DummyBootstrapRecorder2 { + + public RuntimeValue create() { + return new RuntimeValue<>(new ConfigSourceProvider() { + @Override + public Iterable getConfigSources(ClassLoader forClassLoader) { + return Collections.emptyList(); + } + }); + } +} diff --git a/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfig.java b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfig.java new file mode 100644 index 000000000..6b9f6eb21 --- /dev/null +++ b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfig.java @@ -0,0 +1,20 @@ +package io.quarkus.it.bootstrap.config.extension; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.BOOTSTRAP) +public class DummyConfig { + + /** + * dummy name + */ + public String name; + + /** + * dummy times + */ + @ConfigItem(defaultValue = "2") + public Integer times; +} diff --git a/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfigSourceProvider.java b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfigSourceProvider.java new file mode 100644 index 000000000..9756685c0 --- /dev/null +++ b/integration-tests/bootstrap-config/extension/runtime/src/main/java/io/quarkus/it/bootstrap/config/extension/DummyConfigSourceProvider.java @@ -0,0 +1,77 @@ +package io.quarkus.it.bootstrap.config.extension; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.runtime.ApplicationConfig; + +/** + * A dummy provider that returns a single ConfigSource which contains + * as many properties as DummyConfig.times indicates and where the + * key depends on DummyConfig.name + */ +public class DummyConfigSourceProvider implements ConfigSourceProvider { + + private final DummyConfig dummyConfig; + private final ApplicationConfig applicationConfig; + + public DummyConfigSourceProvider(DummyConfig dummyConfig, ApplicationConfig applicationConfig) { + this.dummyConfig = dummyConfig; + this.applicationConfig = applicationConfig; + } + + @Override + public Iterable getConfigSources(ClassLoader forClassLoader) { + InMemoryConfigSource configSource = new InMemoryConfigSource(Integer.MIN_VALUE, "dummy config source"); + for (int i = 0; i < dummyConfig.times; i++) { + configSource.add(dummyConfig.name + ".key.i" + (i + 1), applicationConfig.name.get() + (i + 1)); + } + return Collections.singletonList(configSource); + } + + private static final class InMemoryConfigSource implements ConfigSource { + + private final Map values = new HashMap<>(); + private final int ordinal; + private final String name; + + private InMemoryConfigSource(int ordinal, String name) { + this.ordinal = ordinal; + this.name = name; + } + + public void add(String key, String value) { + values.put(key, value); + } + + @Override + public Map getProperties() { + return values; + } + + @Override + public Set getPropertyNames() { + return values.keySet(); + } + + @Override + public int getOrdinal() { + return ordinal; + } + + @Override + public String getValue(String propertyName) { + return values.get(propertyName); + } + + @Override + public String getName() { + return name; + } + } +} diff --git a/integration-tests/bootstrap-config/pom.xml b/integration-tests/bootstrap-config/pom.xml new file mode 100644 index 000000000..f11e72713 --- /dev/null +++ b/integration-tests/bootstrap-config/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + ../ + + + quarkus-integration-test-bootstrap-config + Quarkus - Integration Tests - Bootstrap Config + pom + + + extension + application + + + \ No newline at end of file diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index bebfba4d2..caee2b865 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -94,6 +94,7 @@ logging-gelf cache qute + bootstrap-config