From d6970e1873ccd776a24cf4445be08abed7aae6d0 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 13 Sep 2021 16:22:38 +0100 Subject: [PATCH] Register @ConfigMappings directly into the Config builder --- .../builditem}/ConfigClassBuildItem.java | 9 +- .../configuration/ConfigMappingUtils.java | 150 ++++++++++++++++++ .../RunTimeConfigurationGenerator.java | 39 +++++ .../steps/ConfigGenerationBuildStep.java | 24 ++- .../runtime/configuration/ConfigUtils.java | 9 ++ .../config/RuntimeInitConfigSource.java | 11 ++ .../StaticInitConfigMappingInvalidTest.java | 39 +++++ .../config/StaticInitConfigMappingTest.java | 37 +++++ .../config/StaticInitConfigSource.java | 13 ++ docs/src/main/asciidoc/config-mappings.adoc | 31 ++++ .../arc/deployment/ConfigBuildStep.java | 139 +--------------- .../HibernateValidatorProcessor.java | 2 +- .../config/ConfigurableExceptionMapper.java | 10 +- .../it/smallrye/config/ExceptionConfig.java | 10 ++ 14 files changed, 384 insertions(+), 139 deletions(-) rename {extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment => core/deployment/src/main/java/io/quarkus/deployment/builditem}/ConfigClassBuildItem.java (82%) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/config/RuntimeInitConfigSource.java create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingInvalidTest.java create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingTest.java create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigSource.java create mode 100644 integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java similarity index 82% rename from extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java rename to core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java index 1d011ddbe5824..0a51d1f063af6 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java @@ -1,8 +1,9 @@ -package io.quarkus.arc.deployment; +package io.quarkus.deployment.builditem; import java.util.Set; import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.annotations.StaticInitSafe; public final class ConfigClassBuildItem extends MultiBuildItem { private final Class configClass; @@ -46,8 +47,12 @@ public boolean isProperties() { return Type.PROPERTIES.equals(type); } + public boolean isStaticInitSafe() { + return configClass.isAnnotationPresent(StaticInitSafe.class); + } + public enum Type { MAPPING, - PROPERTIES; + PROPERTIES } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java new file mode 100644 index 0000000000000..2cbe8dc5f7340 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java @@ -0,0 +1,150 @@ +package io.quarkus.deployment.configuration; + +import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.MAPPING; +import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.PROPERTIES; +import static java.util.Collections.emptySet; +import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; +import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; +import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; +import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ConfigClassBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappingLoader; +import io.smallrye.config.ConfigMappingMetadata; + +public class ConfigMappingUtils { + public static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName()); + + private ConfigMappingUtils() { + } + + @BuildStep + public static void generateConfigClasses( + CombinedIndexBuildItem combinedIndex, + BuildProducer generatedClasses, + BuildProducer reflectiveClasses, + BuildProducer configClasses, + DotName configAnnotation) { + + for (AnnotationInstance instance : combinedIndex.getIndex().getAnnotations(configAnnotation)) { + AnnotationTarget target = instance.target(); + AnnotationValue annotationPrefix = instance.value("prefix"); + + if (target.kind().equals(FIELD)) { + if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { + configClasses.produce( + toConfigClassBuildItem(instance, toClass(target.asField().type().name()), + annotationPrefix.asString())); + continue; + } + } + + if (target.kind().equals(METHOD_PARAMETER)) { + if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { + ClassType classType = target.asMethodParameter().method().parameters() + .get(target.asMethodParameter().position()).asClassType(); + configClasses + .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); + continue; + } + } + + if (!target.kind().equals(CLASS)) { + continue; + } + + Class configClass = toClass(target.asClass().name()); + String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); + + List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); + Set generatedClassesNames = new HashSet<>(); + Set mappingsInfo = new HashSet<>(); + configMappingsMetadata.forEach(mappingMetadata -> { + generatedClasses.produce( + new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes())); + reflectiveClasses + .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build()); + reflectiveClasses + .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build()); + + for (Class parent : getHierarchy(mappingMetadata.getInterfaceType())) { + reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build()); + } + + generatedClassesNames.add(mappingMetadata.getClassName()); + + ClassInfo mappingInfo = combinedIndex.getIndex() + .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); + if (mappingInfo != null) { + mappingsInfo.add(mappingInfo); + } + }); + + // For implicit converters + for (ClassInfo classInfo : mappingsInfo) { + for (MethodInfo method : classInfo.methods()) { + reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString())); + } + } + + configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); + } + } + + private static Class toClass(DotName dotName) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + return classLoader.loadClass(dotName.toString()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("The class (" + dotName.toString() + ") cannot be created during deployment.", e); + } + } + + private static ConfigClassBuildItem toConfigClassBuildItem( + AnnotationInstance instance, + Class configClass, + String prefix) { + return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); + } + + private static ConfigClassBuildItem toConfigClassBuildItem( + AnnotationInstance instance, + Class configClass, + Set generatedClasses, + String prefix) { + if (instance.name().equals(CONFIG_MAPPING_NAME)) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); + } else { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); + } + } + + private static List> getHierarchy(Class mapping) { + List> interfaces = new ArrayList<>(); + for (Class i : mapping.getInterfaces()) { + interfaces.add(i); + interfaces.addAll(getHierarchy(i)); + } + return interfaces; + } +} 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 a519468024707..2ad341c859446 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 @@ -75,6 +75,7 @@ import io.quarkus.runtime.configuration.RuntimeConfigSource; import io.quarkus.runtime.configuration.RuntimeConfigSourceFactory; import io.quarkus.runtime.configuration.RuntimeConfigSourceProvider; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.Converters; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.SmallRyeConfig; @@ -179,6 +180,8 @@ public final class RunTimeConfigurationGenerator { static final MethodDescriptor CU_ADD_SOURCE_FACTORY_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceFactoryProvider", void.class, SmallRyeConfigBuilder.class, ConfigSourceFactoryProvider.class); + static final MethodDescriptor CU_WITH_MAPPING = MethodDescriptor.ofMethod(ConfigUtils.class, "addMapping", + void.class, SmallRyeConfigBuilder.class, String.class, String.class); static final MethodDescriptor RCS_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSource.class, String.class); static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class); @@ -304,6 +307,8 @@ public static final class GenerateOperation implements AutoCloseable { final Set runtimeConfigSources; final Set runtimeConfigSourceProviders; final Set runtimeConfigSourceFactories; + final Set staticConfigMappings; + final Set runtimeConfigMappings; /** * Regular converters organized by type. Each converter is stored in a separate field. Some are used * only at build time, some only at run time, and some at both times. @@ -340,6 +345,8 @@ public static final class GenerateOperation implements AutoCloseable { runtimeConfigSources = builder.getRuntimeConfigSources(); runtimeConfigSourceProviders = builder.getRuntimeConfigSourceProviders(); runtimeConfigSourceFactories = builder.getRuntimeConfigSourceFactories(); + staticConfigMappings = builder.getStaticConfigMappings(); + runtimeConfigMappings = builder.getRuntimeConfigMappings(); cc = ClassCreator.builder().classOutput(classOutput).className(CONFIG_CLASS_NAME).setFinal(true).build(); generateEmptyParsers(cc); // not instantiable @@ -427,6 +434,11 @@ public static final class GenerateOperation implements AutoCloseable { clinit.invokeStaticMethod(CU_ADD_SOURCE_FACTORY_PROVIDER, buildTimeBuilder, clinit.newInstance(RCSF_NEW, clinit.load(discoveredConfigSourceFactory))); } + // add mappings + for (ConfigClassWithPrefix configMapping : staticConfigMappings) { + clinit.invokeStaticMethod(CU_WITH_MAPPING, buildTimeBuilder, + clinit.load(configMapping.getKlass().getName()), clinit.load(configMapping.getPrefix())); + } clinitConfig = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, buildTimeBuilder), SmallRyeConfig.class); @@ -661,6 +673,12 @@ public void run() { readConfig.newInstance(RCSF_NEW, readConfig.load(discoveredConfigSourceFactory))); } + // add mappings + for (ConfigClassWithPrefix configMapping : runtimeConfigMappings) { + readConfig.invokeStaticMethod(CU_WITH_MAPPING, runTimeBuilder, + readConfig.load(configMapping.getKlass().getName()), readConfig.load(configMapping.getPrefix())); + } + ResultHandle bootstrapConfig = null; if (bootstrapConfigSetupNeeded()) { bootstrapConfig = readBootstrapConfig.invokeVirtualMethod(SRCB_BUILD, bootstrapBuilder); @@ -1677,6 +1695,9 @@ public static final class Builder { private Set runtimeConfigSourceProviders; private Set runtimeConfigSourceFactories; + private Set staticConfigMappings; + private Set runtimeConfigMappings; + Builder() { } @@ -1784,6 +1805,24 @@ public Builder setRuntimeConfigSourceFactories(final Set runtimeConfigSo return this; } + Set getStaticConfigMappings() { + return staticConfigMappings; + } + + public Builder setStaticConfigMappings(final Set staticConfigMappings) { + this.staticConfigMappings = staticConfigMappings; + return this; + } + + Set getRuntimeConfigMappings() { + return runtimeConfigMappings; + } + + public Builder setRuntimeConfigMappings(final Set runtimeConfigMappings) { + this.runtimeConfigMappings = runtimeConfigMappings; + return this; + } + public GenerateOperation build() { return new GenerateOperation(this); } 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 4efa12878680f..e21d9716ad89a 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 @@ -2,7 +2,9 @@ import static io.quarkus.deployment.steps.ConfigBuildSteps.SERVICES_PREFIX; import static io.quarkus.deployment.util.ServiceUtil.classNamesNamedIn; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -31,6 +33,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalBootstrapConfigSourceProviderBuildItem; import io.quarkus.deployment.builditem.AdditionalStaticInitConfigSourceProviderBuildItem; +import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -57,6 +60,7 @@ import io.quarkus.runtime.configuration.ConfigUtils; import io.quarkus.runtime.configuration.ConfigurationRuntimeConfig; import io.quarkus.runtime.configuration.RuntimeOverrideConfigSource; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.ConfigSourceFactory; import io.smallrye.config.PropertiesLocationConfigSourceFactory; @@ -109,7 +113,8 @@ void generateConfigClass( LiveReloadBuildItem liveReloadBuildItem, List additionalBootstrapConfigSourceProviders, List staticInitConfigSourceProviders, - List staticInitConfigSourceFactories) + List staticInitConfigSourceFactories, + List configClasses) throws IOException { if (liveReloadBuildItem.isLiveReload()) { @@ -145,6 +150,8 @@ void generateConfigClass( .setRuntimeConfigSources(discoveredConfigSources) .setRuntimeConfigSourceProviders(discoveredConfigSourceProviders) .setRuntimeConfigSourceFactories(discoveredConfigSourceFactories) + .setStaticConfigMappings(staticSafeConfigMappings(configClasses)) + .setRuntimeConfigMappings(runtimeConfigMappings(configClasses)) .build() .run(); } @@ -262,4 +269,19 @@ private static Set staticSafeServices(Set services) { } return staticSafe; } + + private static Set staticSafeConfigMappings(List configClasses) { + return configClasses.stream() + .filter(ConfigClassBuildItem::isMapping) + .filter(ConfigClassBuildItem::isStaticInitSafe) + .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) + .collect(toSet()); + } + + private static Set runtimeConfigMappings(List configClasses) { + return configClasses.stream() + .filter(ConfigClassBuildItem::isMapping) + .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) + .collect(toSet()); + } } 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 125d8e2b8a610..13a39f01b795f 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 @@ -217,6 +217,15 @@ public static Map loadRuntimeDefaultValues() { } } + public static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + /** * Checks if a property is present in the current Configuration. *

diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/config/RuntimeInitConfigSource.java b/core/test-extension/deployment/src/test/java/io/quarkus/config/RuntimeInitConfigSource.java new file mode 100644 index 0000000000000..9e8ef943f4141 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/config/RuntimeInitConfigSource.java @@ -0,0 +1,11 @@ +package io.quarkus.config; + +import java.util.Map; + +import io.smallrye.config.common.MapBackedConfigSource; + +public class RuntimeInitConfigSource extends MapBackedConfigSource { + public RuntimeInitConfigSource() { + super("", Map.of("config.static.init.my-prop", "1234")); + } +} diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingInvalidTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingInvalidTest.java new file mode 100644 index 0000000000000..4f0f897bdd813 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingInvalidTest.java @@ -0,0 +1,39 @@ +package io.quarkus.config; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; + +public class StaticInitConfigMappingInvalidTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(RuntimeInitConfigSource.class) + .addAsServiceProvider("org.eclipse.microprofile.config.spi.ConfigSource", + RuntimeInitConfigSource.class.getName())) + .assertException(throwable -> { + }); + + @Inject + StaticInitConfigMapping mapping; + + @Test + void fail() { + Assertions.fail(); + } + + @StaticInitSafe + @ConfigMapping(prefix = "config.static.init") + public interface StaticInitConfigMapping { + // The configuration does not exist at static init, so the startup will fail + String myProp(); + } +} diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingTest.java new file mode 100644 index 0000000000000..fed4b968472cc --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigMappingTest.java @@ -0,0 +1,37 @@ +package io.quarkus.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; + +public class StaticInitConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(StaticInitConfigSource.class) + .addAsServiceProvider("org.eclipse.microprofile.config.spi.ConfigSource", + StaticInitConfigSource.class.getName())); + + // This does not come from the static init Config, but it is registered in both static and runtime. + // If it doesn't fail, it means that the static mapping was done correctly. + @Inject + StaticInitConfigMapping mapping; + + @Test + void staticInitMapping() { + assertEquals("1234", mapping.myProp()); + } + + @ConfigMapping(prefix = "config.static.init") + public interface StaticInitConfigMapping { + String myProp(); + } +} diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigSource.java b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigSource.java new file mode 100644 index 0000000000000..88071fa326e10 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/config/StaticInitConfigSource.java @@ -0,0 +1,13 @@ +package io.quarkus.config; + +import java.util.Map; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.smallrye.config.common.MapBackedConfigSource; + +@StaticInitSafe +public class StaticInitConfigSource extends MapBackedConfigSource { + public StaticInitConfigSource() { + super("", Map.of("config.static.init.my-prop", "1234")); + } +} diff --git a/docs/src/main/asciidoc/config-mappings.adoc b/docs/src/main/asciidoc/config-mappings.adoc index 3d44d00407352..0d10db69b6a5a 100644 --- a/docs/src/main/asciidoc/config-mappings.adoc +++ b/docs/src/main/asciidoc/config-mappings.adoc @@ -38,6 +38,37 @@ prefix, and the method name with `.` (dot) as the separator. NOTE: If a mapping fails to match a configuration property a `NoSuchElementException` is thrown, unless the mapped element is an `Optional`. +=== Registration + +When a Quarkus application starts, a config mapping can be registered twice. One time for _STATIC INIT_ and a second +time for _RUNTIME INIT_: + +==== STATIC INIT + +Quarkus starts some of its services during static initialization, and `Config` is usually one of the first things that +is created. In certain situations it may not be possible to correctly initialize a config mapping. For instance, if the +mapping requires values from a custom `ConfigSource`. For this reason, any config mapping requires the annotation +`@io.quarkus.runtime.configuration.StaticInitSafe` to mark the mapping as safe to be used at this stage. Learn more +about xref:config-extending-support.adoc#custom-config-source[registration] of a custom `ConfigSource`. + +===== Example + +[source,java] +---- +@StaticInitSafe +@ConfigMapping(prefix = "server") +interface Server { + String host(); + + int port(); +} +---- + +==== RUNTIME INIT + +The _RUNTIME INIT_ stage happens after _STATIC INIT_. There are no restrictions at this stage, and any config mapping +is added to the `Config` instance as expected. + === Retrieval A config mapping interface can be injected into any CDI aware bean: diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index a0d855f1ce38f..ee60809ab577f 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -1,17 +1,12 @@ package io.quarkus.arc.deployment; -import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.MAPPING; -import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.PROPERTIES; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME; import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; -import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; -import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; import static org.jboss.jandex.AnnotationInstance.create; import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; -import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; -import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER; import static org.jboss.jandex.AnnotationValue.createStringValue; import java.util.ArrayList; @@ -19,7 +14,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; @@ -31,10 +25,8 @@ import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; @@ -54,16 +46,15 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.configuration.ConfigMappingUtils; import io.quarkus.deployment.configuration.definition.RootDefinition; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.annotations.ConfigPhase; -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.ConfigMappingLoader; -import io.smallrye.config.ConfigMappingMetadata; import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.inject.ConfigProducer; @@ -75,7 +66,6 @@ public class ConfigBuildStep { private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); - private static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName()); private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName()); private static final DotName SET_NAME = DotName.createSimple(Set.class.getName()); private static final DotName LIST_NAME = DotName.createSimple(List.class.getName()); @@ -252,79 +242,11 @@ void generateConfigClasses( BuildProducer reflectiveClasses, BuildProducer configClasses) { - List mappingAnnotations = new ArrayList<>(); - mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(CONFIG_MAPPING_NAME)); - mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(MP_CONFIG_PROPERTIES_NAME)); - - for (AnnotationInstance instance : mappingAnnotations) { - AnnotationTarget target = instance.target(); - AnnotationValue annotationPrefix = instance.value("prefix"); - - if (target.kind().equals(FIELD)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - configClasses.produce( - toConfigClassBuildItem(instance, toClass(target.asField().type().name()), - annotationPrefix.asString())); - continue; - } - } - - if (target.kind().equals(METHOD_PARAMETER)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - ClassType classType = target.asMethodParameter().method().parameters() - .get(target.asMethodParameter().position()).asClassType(); - configClasses - .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); - continue; - } - } - - if (!target.kind().equals(CLASS)) { - continue; - } - - Class configClass = toClass(target.asClass().name()); - String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); - - List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); - Set generatedClassesNames = new HashSet<>(); - Set mappingsInfo = new HashSet<>(); - configMappingsMetadata.forEach(mappingMetadata -> { - generatedClasses.produce( - new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes())); - reflectiveClasses - .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build()); - reflectiveClasses - .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build()); - - for (Class parent : getHierarchy(mappingMetadata.getInterfaceType())) { - reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build()); - } - - generatedClassesNames.add(mappingMetadata.getClassName()); - - ClassInfo mappingInfo = combinedIndex.getIndex() - .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); - if (mappingInfo != null) { - mappingsInfo.add(mappingInfo); - } - }); - - // Search and register possible classes for implicit Converter methods - for (ClassInfo classInfo : mappingsInfo) { - for (MethodInfo method : classInfo.methods()) { - if (!isHandledByProducers(method.returnType()) && - mappingsInfo.stream() - .map(ClassInfo::name) - .noneMatch(name -> name.equals(method.returnType().name()))) { - reflectiveClasses - .produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString())); - } - } - } - - configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); - } + // TODO - Generation of Mapping interface classes can be done in core because they don't require CDI + ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, + CONFIG_MAPPING_NAME); + ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, + MP_CONFIG_PROPERTIES_NAME); } @BuildStep @@ -405,12 +327,6 @@ void registerConfigClasses( configClassWithPrefix -> Stream.of(configClassWithPrefix.getKlass(), configClassWithPrefix.getPrefix()) .collect(toList())); - recorder.registerConfigMappings( - configClasses.stream() - .filter(ConfigClassBuildItem::isMapping) - .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) - .collect(toSet())); - recorder.registerConfigProperties( configClasses.stream() .filter(ConfigClassBuildItem::isProperties) @@ -419,45 +335,6 @@ void registerConfigClasses( .collect(toSet())); } - private static Class toClass(DotName dotName) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - return classLoader.loadClass(dotName.toString()); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("The class (" + dotName.toString() + ") cannot be created during deployment.", e); - } - } - - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - String prefix) { - return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); - } - - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - Set generatedClasses, - String prefix) { - if (instance.name().equals(CONFIG_MAPPING_NAME)) { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); - } else if (instance.name().equals(MP_CONFIG_PROPERTIES_NAME)) { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); - } else { - throw new IllegalArgumentException(); - } - } - - private static List> getHierarchy(Class mapping) { - List> interfaces = new ArrayList<>(); - for (Class i : mapping.getInterfaces()) { - interfaces.add(i); - interfaces.addAll(getHierarchy(i)); - } - return interfaces; - } - private String getPropertyName(String name, ClassInfo declaringClass) { StringBuilder builder = new StringBuilder(); if (declaringClass.enclosingClass() == null) { diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index 2be5f831516fb..41ae49a227b3d 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -49,7 +49,6 @@ import io.quarkus.arc.deployment.AutoAddScopeBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; -import io.quarkus.arc.deployment.ConfigClassBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.BeanInfo; @@ -63,6 +62,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java index ff1f0f1125489..8a1e5132fd993 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java @@ -13,13 +13,15 @@ public class ConfigurableExceptionMapper @Inject @ConfigProperty(name = "exception.message") String message; - - public ConfigurableExceptionMapper() { - System.out.println("ConfigurableExceptionMapper.ConfigurableExceptionMapper"); - } + @Inject + ExceptionConfig exceptionConfig; @Override public Response toResponse(final ConfigurableExceptionMapperException exception) { + if (!message.equals(exceptionConfig.message())) { + return Response.serverError().build(); + } + return Response.ok().entity(message).build(); } diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java new file mode 100644 index 0000000000000..3ae0dd6407391 --- /dev/null +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java @@ -0,0 +1,10 @@ +package io.quarkus.it.smallrye.config; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.smallrye.config.ConfigMapping; + +@StaticInitSafe +@ConfigMapping(prefix = "exception") +public interface ExceptionConfig { + String message(); +}