diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 53acbb19b37937..bb7f0f689bb7c3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -42,7 +42,7 @@ 2.0 1.2 1.6.0 - 2.4.2 + 2.4.3-SNAPSHOT 3.1.1 3.0.1 2.1.9 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 3451bbf361306e..7ed31a53809ef4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -89,6 +89,7 @@ import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigUtils; import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.KeyMap; import io.smallrye.config.KeyMapBackedConfigSource; import io.smallrye.config.NameIterator; @@ -164,6 +165,10 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, builder.withSources(ds1, ds2, platformConfigSource, pcs); } + for (ConfigClassWithPrefix mapping : reader.getBuildTimeVisibleMappings()) { + builder.withMapping(mapping.getKlass(), mapping.getPrefix()); + } + if (configCustomizer != null) { configCustomizer.accept(builder); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigDescriptionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigDescriptionBuildItem.java index 4f50e314502380..abf783202df0f2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigDescriptionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigDescriptionBuildItem.java @@ -9,13 +9,11 @@ public final class ConfigDescriptionBuildItem extends MultiBuildItem implements Comparable { private final String propertyName; - private final Class type; private final String defaultValue; private final String docs; - public ConfigDescriptionBuildItem(String propertyName, Class type, String defaultValue, String docs) { + public ConfigDescriptionBuildItem(String propertyName, String defaultValue, String docs) { this.propertyName = propertyName; - this.type = type; this.defaultValue = defaultValue; this.docs = docs; } @@ -24,10 +22,6 @@ public String getPropertyName() { return propertyName; } - public Class getType() { - return type; - } - public String getDefaultValue() { return defaultValue; } 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 57abae96f853c2..d1ce6c6cfcabf8 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 @@ -6,6 +6,7 @@ import static io.quarkus.deployment.util.ReflectUtil.toError; import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; import static io.quarkus.deployment.util.ReflectUtil.unwrapInvocationTargetException; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -55,6 +56,10 @@ import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.runtime.configuration.HyphenateEnumConverter; import io.quarkus.runtime.configuration.NameIterator; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappings; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; +import io.smallrye.config.ConfigValue; import io.smallrye.config.Converters; import io.smallrye.config.EnvConfigSource; import io.smallrye.config.Expressions; @@ -69,14 +74,19 @@ public final class BuildTimeConfigurationReader { final ConfigPatternMap buildTimePatternMap; final ConfigPatternMap buildTimeRunTimePatternMap; - final ConfigPatternMap bootstrapPatternMap; final ConfigPatternMap runTimePatternMap; + final ConfigPatternMap bootstrapPatternMap; - final List buildTimeVisibleRoots; final List allRoots; + final List buildTimeVisibleRoots; final boolean bootstrapRootsEmpty; + final List buildTimeMappings; + final List buildTimeRunTimeMappings; + final List runTimeMappings; + final List buildTimeVisibleMappings; + /** * Construct a new instance. * @@ -85,12 +95,42 @@ 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<>(); + List buildTimeRunTimeRoots = new ArrayList<>(); + List runTimeRoots = new ArrayList<>(); + List bootstrapRoots = new ArrayList<>(); + + buildTimeMappings = new ArrayList<>(); + buildTimeRunTimeMappings = new ArrayList<>(); + runTimeMappings = new ArrayList<>(); + Map, GroupDefinition> groups = new HashMap<>(); for (Class configRoot : configRoots) { + boolean isMapping = configRoot.isAnnotationPresent(ConfigMapping.class); + if (isMapping) { + ConfigPhase phase = ConfigPhase.BUILD_TIME; + // To retrieve config phase + ConfigRoot annotation = configRoot.getAnnotation(ConfigRoot.class); + if (annotation != null) { + if (!annotation.name().equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { + throw reportError(configRoot, "@ConfigRoot.name() is not allowed in combination with @ConfigMapping"); + } + + phase = annotation.phase(); + } + + ConfigClassWithPrefix mapping = configClassWithPrefix(configRoot); + if (phase.equals(ConfigPhase.BUILD_TIME)) { + buildTimeMappings.add(mapping); + } else if (phase.equals(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) { + buildTimeRunTimeMappings.add(mapping); + } else if (phase.equals(ConfigPhase.RUN_TIME)) { + runTimeMappings.add(mapping); + } + + continue; + } + String name = ConfigItem.HYPHENATED_ELEMENT_NAME; ConfigPhase phase = ConfigPhase.BUILD_TIME; ConfigRoot annotation = configRoot.getAnnotation(ConfigRoot.class); @@ -115,10 +155,11 @@ public BuildTimeConfigurationReader(final List> configRoots) { } } - bootstrapPatternMap = PatternMapBuilder.makePatterns(bootstrapRoots); - runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots); - buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots); + // ConfigRoots buildTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRoots); + buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots); + runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots); + bootstrapPatternMap = PatternMapBuilder.makePatterns(bootstrapRoots); buildTimeVisibleRoots = new ArrayList<>(buildTimeRoots.size() + buildTimeRunTimeRoots.size()); buildTimeVisibleRoots.addAll(buildTimeRoots); @@ -126,13 +167,15 @@ public BuildTimeConfigurationReader(final List> configRoots) { bootstrapRootsEmpty = bootstrapRoots.isEmpty(); - List allRoots = new ArrayList<>( - buildTimeVisibleRoots.size() + bootstrapRoots.size() + runTimeRoots.size()); + allRoots = new ArrayList<>(buildTimeVisibleRoots.size() + bootstrapRoots.size() + runTimeRoots.size()); allRoots.addAll(buildTimeVisibleRoots); allRoots.addAll(bootstrapRoots); allRoots.addAll(runTimeRoots); - this.allRoots = allRoots; + // ConfigMappings + buildTimeVisibleMappings = new ArrayList<>(buildTimeMappings.size() + buildTimeRunTimeMappings.size()); + buildTimeVisibleMappings.addAll(buildTimeMappings); + buildTimeVisibleMappings.addAll(buildTimeRunTimeMappings); } private static void processClass(ClassDefinition.Builder builder, Class clazz, @@ -251,6 +294,18 @@ public List getAllRoots() { return allRoots; } + public List getBuildTimeMappings() { + return buildTimeMappings; + } + + public List getBuildTimeRunTimeMappings() { + return buildTimeRunTimeMappings; + } + + public List getBuildTimeVisibleMappings() { + return buildTimeVisibleMappings; + } + public ReadResult readConfiguration(final SmallRyeConfig config) { return new ReadOperation(config).run(); } @@ -301,7 +356,8 @@ ReadResult run() { } // sweep-up SmallRyeConfig runtimeDefaultsConfig = getConfigForRuntimeDefaults(); - for (String propertyName : getAllProperties()) { + Set allProperties = getAllProperties(); + for (String propertyName : allProperties) { if (propertyName.equals(ConfigSource.CONFIG_ORDINAL)) { continue; } @@ -393,10 +449,60 @@ ReadResult run() { () -> runtimeDefaultsConfig.getOptionalValue(propertyName, String.class).orElse(""))); } } - return new ReadResult(objectsByRootClass, specifiedRunTimeDefaultValues, buildTimeRunTimeVisibleValues, - allBuildTimeValues, - buildTimePatternMap, buildTimeRunTimePatternMap, bootstrapPatternMap, runTimePatternMap, allRoots, - bootstrapRootsEmpty); + + // ConfigMappings + for (ConfigClassWithPrefix mapping : buildTimeVisibleMappings) { + objectsByRootClass.put(mapping.getKlass(), config.getConfigMapping(mapping.getKlass(), mapping.getPrefix())); + } + + // Build Time Values Recording + for (ConfigClassWithPrefix mapping : buildTimeMappings) { + Set mappedProperties = ConfigMappings.mappedProperties(mapping, allProperties); + for (String property : mappedProperties) { + ConfigValue value = config.getConfigValue(property); + if (value != null && value.getRawValue() != null) { + allBuildTimeValues.put(property, value.getRawValue()); + } + } + } + + // Build Time and Run Time Values Recording + for (ConfigClassWithPrefix mapping : buildTimeRunTimeMappings) { + Set mappedProperties = ConfigMappings.mappedProperties(mapping, allProperties); + for (String property : mappedProperties) { + ConfigValue value = config.getConfigValue(property); + if (value != null && value.getRawValue() != null) { + allBuildTimeValues.put(property, value.getRawValue()); + buildTimeRunTimeVisibleValues.put(property, value.getRawValue()); + } + } + } + + // Run Time Values Recording + for (ConfigClassWithPrefix mapping : runTimeMappings) { + Set mappedProperties = ConfigMappings.mappedProperties(mapping, allProperties); + for (String property : mappedProperties) { + ConfigValue value = config.getConfigValue(property); + if (value != null && value.getRawValue() != null) { + specifiedRunTimeDefaultValues.put(property, value.getRawValue()); + } + } + } + + return new ReadResult.Builder().setObjectsByRootClass(objectsByRootClass) + .setAllBuildTimeValues(allBuildTimeValues) + .setBuildTimeRunTimeVisibleValues(buildTimeRunTimeVisibleValues) + .setSpecifiedRunTimeDefaultValues(specifiedRunTimeDefaultValues) + .setBuildTimePatternMap(buildTimePatternMap) + .setBuildTimeRunTimePatternMap(buildTimeRunTimePatternMap) + .setBootstrapPatternMap(bootstrapPatternMap) + .setRunTimePatternMap(runTimePatternMap) + .setAllRoots(allRoots) + .setBootstrapRootsEmpty(bootstrapRootsEmpty) + .setBuildTimeMappings(buildTimeMappings) + .setBuildTimeRunTimeMappings(buildTimeRunTimeMappings) + .setRunTimeMappings(runTimeMappings) + .createReadResult(); } /** @@ -769,62 +875,67 @@ private SmallRyeConfig getConfigForRuntimeDefaults() { public static final class ReadResult { final Map, Object> objectsByRootClass; - final Map specifiedRunTimeDefaultValues; - final Map buildTimeRunTimeVisibleValues; + final Map allBuildTimeValues; + final Map buildTimeRunTimeVisibleValues; + final Map specifiedRunTimeDefaultValues; + 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, - Map allBuildTimeValues, - final ConfigPatternMap buildTimePatternMap, - final ConfigPatternMap buildTimeRunTimePatternMap, - 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; - this.allBuildTimeValues = allBuildTimeValues; + final List allRoots; + final Map, RootDefinition> runTimeRootsByClass; + + final List buildTimeMappings; + final List buildTimeRunTimeMappings; + final List runTimeMappings; + + public ReadResult(final Builder builder) { + this.objectsByRootClass = builder.getObjectsByRootClass(); + + this.allBuildTimeValues = builder.getAllBuildTimeValues(); + this.buildTimeRunTimeVisibleValues = builder.getBuildTimeRunTimeVisibleValues(); + this.specifiedRunTimeDefaultValues = builder.getSpecifiedRunTimeDefaultValues(); + + this.buildTimePatternMap = builder.getBuildTimePatternMap(); + this.buildTimeRunTimePatternMap = builder.getBuildTimeRunTimePatternMap(); + this.bootstrapPatternMap = builder.getBootstrapPatternMap(); + this.runTimePatternMap = builder.getRunTimePatternMap(); + + this.bootstrapRootsEmpty = builder.isBootstrapRootsEmpty(); + + this.allRoots = builder.getAllRoots(); Map, RootDefinition> map = new HashMap<>(); for (RootDefinition root : allRoots) { map.put(root.getConfigurationClass(), root); } - runTimeRootsByClass = map; + this.runTimeRootsByClass = map; + + this.buildTimeMappings = builder.getBuildTimeMappings(); + this.buildTimeRunTimeMappings = builder.getBuildTimeRunTimeMappings(); + this.runTimeMappings = builder.getRunTimeMappings(); } public Map, Object> getObjectsByRootClass() { return objectsByRootClass; } - public Object requireRootObjectForClass(Class clazz) { - Object obj = objectsByRootClass.get(clazz); - if (obj == null) { - throw new IllegalStateException("No root found for " + clazz); - } - return obj; - } - - public Map getSpecifiedRunTimeDefaultValues() { - return specifiedRunTimeDefaultValues; + public Map getAllBuildTimeValues() { + return allBuildTimeValues; } public Map getBuildTimeRunTimeVisibleValues() { return buildTimeRunTimeVisibleValues; } + public Map getSpecifiedRunTimeDefaultValues() { + return specifiedRunTimeDefaultValues; + } + public ConfigPatternMap getBuildTimePatternMap() { return buildTimePatternMap; } @@ -841,16 +952,36 @@ public ConfigPatternMap getRunTimePatternMap() { return runTimePatternMap; } - public Map getAllBuildTimeValues() { - return allBuildTimeValues; + public boolean isBootstrapRootsEmpty() { + return bootstrapRootsEmpty; } public List getAllRoots() { return allRoots; } - public boolean isBootstrapRootsEmpty() { - return bootstrapRootsEmpty; + public Map, RootDefinition> getRunTimeRootsByClass() { + return runTimeRootsByClass; + } + + public List getBuildTimeMappings() { + return buildTimeMappings; + } + + public List getBuildTimeRunTimeMappings() { + return buildTimeRunTimeMappings; + } + + public List getRunTimeMappings() { + return runTimeMappings; + } + + public Object requireRootObjectForClass(Class clazz) { + Object obj = objectsByRootClass.get(clazz); + if (obj == null) { + throw new IllegalStateException("No root found for " + clazz); + } + return obj; } public RootDefinition requireRootDefinitionForClass(Class clazz) { @@ -860,5 +991,142 @@ public RootDefinition requireRootDefinitionForClass(Class clazz) { } return def; } + + static class Builder { + private Map, Object> objectsByRootClass; + private Map allBuildTimeValues; + private Map buildTimeRunTimeVisibleValues; + private Map specifiedRunTimeDefaultValues; + private ConfigPatternMap buildTimePatternMap; + private ConfigPatternMap buildTimeRunTimePatternMap; + private ConfigPatternMap bootstrapPatternMap; + private ConfigPatternMap runTimePatternMap; + private List allRoots; + private boolean bootstrapRootsEmpty; + private List buildTimeMappings; + private List buildTimeRunTimeMappings; + private List runTimeMappings; + + Map, Object> getObjectsByRootClass() { + return objectsByRootClass; + } + + Builder setObjectsByRootClass(final Map, Object> objectsByRootClass) { + this.objectsByRootClass = objectsByRootClass; + return this; + } + + Map getAllBuildTimeValues() { + return allBuildTimeValues; + } + + Builder setAllBuildTimeValues(final Map allBuildTimeValues) { + this.allBuildTimeValues = allBuildTimeValues; + return this; + } + + Map getBuildTimeRunTimeVisibleValues() { + return buildTimeRunTimeVisibleValues; + } + + Builder setBuildTimeRunTimeVisibleValues(final Map buildTimeRunTimeVisibleValues) { + this.buildTimeRunTimeVisibleValues = buildTimeRunTimeVisibleValues; + return this; + } + + Map getSpecifiedRunTimeDefaultValues() { + return specifiedRunTimeDefaultValues; + } + + Builder setSpecifiedRunTimeDefaultValues(final Map specifiedRunTimeDefaultValues) { + this.specifiedRunTimeDefaultValues = specifiedRunTimeDefaultValues; + return this; + } + + ConfigPatternMap getBuildTimePatternMap() { + return buildTimePatternMap; + } + + Builder setBuildTimePatternMap(final ConfigPatternMap buildTimePatternMap) { + this.buildTimePatternMap = buildTimePatternMap; + return this; + } + + ConfigPatternMap getBuildTimeRunTimePatternMap() { + return buildTimeRunTimePatternMap; + } + + Builder setBuildTimeRunTimePatternMap(final ConfigPatternMap buildTimeRunTimePatternMap) { + this.buildTimeRunTimePatternMap = buildTimeRunTimePatternMap; + return this; + } + + ConfigPatternMap getBootstrapPatternMap() { + return bootstrapPatternMap; + } + + Builder setBootstrapPatternMap(final ConfigPatternMap bootstrapPatternMap) { + this.bootstrapPatternMap = bootstrapPatternMap; + return this; + } + + ConfigPatternMap getRunTimePatternMap() { + return runTimePatternMap; + } + + Builder setRunTimePatternMap(final ConfigPatternMap runTimePatternMap) { + this.runTimePatternMap = runTimePatternMap; + return this; + } + + List getAllRoots() { + return allRoots; + } + + Builder setAllRoots(final List allRoots) { + this.allRoots = allRoots; + return this; + } + + boolean isBootstrapRootsEmpty() { + return bootstrapRootsEmpty; + } + + Builder setBootstrapRootsEmpty(final boolean bootstrapRootsEmpty) { + this.bootstrapRootsEmpty = bootstrapRootsEmpty; + return this; + } + + List getBuildTimeMappings() { + return buildTimeMappings; + } + + Builder setBuildTimeMappings(final List buildTimeMappings) { + this.buildTimeMappings = buildTimeMappings; + return this; + } + + List getBuildTimeRunTimeMappings() { + return buildTimeRunTimeMappings; + } + + Builder setBuildTimeRunTimeMappings(final List buildTimeRunTimeMappings) { + this.buildTimeRunTimeMappings = buildTimeRunTimeMappings; + return this; + } + + List getRunTimeMappings() { + return runTimeMappings; + } + + Builder setRunTimeMappings(final List runTimeMappings) { + this.runTimeMappings = runTimeMappings; + return this; + } + + ReadResult createReadResult() { + return new ReadResult(this); + } + } } } 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 index 2cbe8dc5f73405..556c9434e00881 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java @@ -23,7 +23,6 @@ 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; @@ -38,7 +37,6 @@ public class ConfigMappingUtils { private ConfigMappingUtils() { } - @BuildStep public static void generateConfigClasses( CombinedIndexBuildItem combinedIndex, BuildProducer generatedClasses, @@ -46,6 +44,7 @@ public static void generateConfigClasses( BuildProducer configClasses, DotName configAnnotation) { + boolean isMapping = configAnnotation.equals(CONFIG_MAPPING_NAME); for (AnnotationInstance instance : combinedIndex.getIndex().getAnnotations(configAnnotation)) { AnnotationTarget target = instance.target(); AnnotationValue annotationPrefix = instance.value("prefix"); @@ -53,8 +52,8 @@ public static void generateConfigClasses( if (target.kind().equals(FIELD)) { if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { configClasses.produce( - toConfigClassBuildItem(instance, toClass(target.asField().type().name()), - annotationPrefix.asString())); + toConfigClassBuildItem(toClass(target.asField().type().name()), + annotationPrefix.asString(), isMapping)); continue; } } @@ -64,7 +63,7 @@ public static void generateConfigClasses( ClassType classType = target.asMethodParameter().method().parameters() .get(target.asMethodParameter().position()).asClassType(); configClasses - .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); + .produce(toConfigClassBuildItem(toClass(classType.name()), annotationPrefix.asString(), isMapping)); continue; } } @@ -75,40 +74,54 @@ public static void generateConfigClasses( Class configClass = toClass(target.asClass().name()); String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); + processConfigClass(configClass, prefix, isMapping, combinedIndex, true, generatedClasses, reflectiveClasses, + configClasses); + } + } - 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()); + public static void processConfigClass( + Class configClass, + String prefix, + boolean isMapping, + CombinedIndexBuildItem combinedIndex, + boolean isApplicationClass, + BuildProducer generatedClasses, + BuildProducer reflectiveClasses, + BuildProducer configClasses) { + + List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); + Set generatedClassesNames = new HashSet<>(); + Set mappingsInfo = new HashSet<>(); + configMappingsMetadata.forEach(mappingMetadata -> { + generatedClasses.produce( + new GeneratedClassBuildItem(isApplicationClass, 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()); + } - ClassInfo mappingInfo = combinedIndex.getIndex() - .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); - if (mappingInfo != null) { - mappingsInfo.add(mappingInfo); - } - }); + generatedClassesNames.add(mappingMetadata.getClassName()); - // For implicit converters - for (ClassInfo classInfo : mappingsInfo) { - for (MethodInfo method : classInfo.methods()) { - reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString())); - } + ClassInfo mappingInfo = combinedIndex.getIndex() + .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); + if (mappingInfo != null) { + mappingsInfo.add(mappingInfo); } + }); - configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); + // 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(configClass, prefix, isMapping, generatedClassesNames)); } private static Class toClass(DotName dotName) { @@ -121,22 +134,18 @@ private static Class toClass(DotName dotName) { } private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, Class configClass, - String prefix) { - return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); + String prefix, + boolean isMapping) { + return toConfigClassBuildItem(configClass, prefix, isMapping, emptySet()); } 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); - } + String prefix, + boolean isMapping, + Set generatedClasses) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, isMapping ? MAPPING : PROPERTIES); } private static List> getHierarchy(Class mapping) { 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 377bac70e18c28..25db34905b30f7 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 @@ -113,8 +113,10 @@ public final class RunTimeConfigurationGenerator { static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "specifiedRunTimeConfigSource", ConfigSource.class); - static final FieldDescriptor C_UNUSED = FieldDescriptor.of(CONFIG_CLASS_NAME, "unused", List.class); - static final FieldDescriptor C_UNUSED_RUNTIME = FieldDescriptor.of(CONFIG_CLASS_NAME, "unusedRuntime", List.class); + static final FieldDescriptor C_UNKNOWN = FieldDescriptor.of(CONFIG_CLASS_NAME, "unknown", List.class); + static final FieldDescriptor C_UNKNOWN_RUNTIME = FieldDescriptor.of(CONFIG_CLASS_NAME, "unknownRuntime", List.class); + static final MethodDescriptor C_REPORT_UNKNOWN = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "reportUnknown", void.class, + String.class, List.class); static final MethodDescriptor CD_INVALID_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "invalidValue", void.class, String.class, IllegalArgumentException.class); @@ -182,6 +184,9 @@ public final class RunTimeConfigurationGenerator { static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class); static final MethodDescriptor RCSF_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceFactory.class, String.class); + static final MethodDescriptor CS_GET_VALUE = MethodDescriptor.ofMethod(ConfigSource.class, "getValue", String.class, + String.class); + static final MethodDescriptor AL_NEW = MethodDescriptor.ofConstructor(ArrayList.class); static final MethodDescriptor AL_ADD = MethodDescriptor.ofMethod(ArrayList.class, "add", boolean.class, Object.class); @@ -283,6 +288,7 @@ public static final class GenerateOperation implements AutoCloseable { final ResultHandle clinitNameBuilder; final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult; final List roots; + final Map allBuildTimeValues; // default values given in the build configuration final Map specifiedRunTimeDefaultValues; final Map buildTimeRunTimeVisibleValues; @@ -324,6 +330,7 @@ public static final class GenerateOperation implements AutoCloseable { this.devMode = builder.launchMode == LaunchMode.DEVELOPMENT; final BuildTimeConfigurationReader.ReadResult buildTimeReadResult = builder.buildTimeReadResult; buildTimeConfigResult = Assert.checkNotNullParam("buildTimeReadResult", buildTimeReadResult); + allBuildTimeValues = Assert.checkNotNullParam("allBuildTimeValues", buildTimeReadResult.getAllBuildTimeValues()); specifiedRunTimeDefaultValues = Assert.checkNotNullParam("specifiedRunTimeDefaultValues", buildTimeReadResult.getSpecifiedRunTimeDefaultValues()); buildTimeRunTimeVisibleValues = Assert.checkNotNullParam("buildTimeRunTimeVisibleValues", @@ -359,11 +366,11 @@ public static final class GenerateOperation implements AutoCloseable { clinit = cc.getMethodCreator(MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "", void.class)); clinit.setModifiers(Opcodes.ACC_STATIC); - cc.getFieldCreator(C_UNUSED).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - clinit.writeStaticField(C_UNUSED, clinit.newInstance(AL_NEW)); + cc.getFieldCreator(C_UNKNOWN).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_UNKNOWN, clinit.newInstance(AL_NEW)); - cc.getFieldCreator(C_UNUSED_RUNTIME).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - clinit.writeStaticField(C_UNUSED_RUNTIME, clinit.newInstance(AL_NEW)); + cc.getFieldCreator(C_UNKNOWN_RUNTIME).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_UNKNOWN_RUNTIME, clinit.newInstance(AL_NEW)); clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); clinitNameBuilder = clinit.newInstance(SB_NEW); @@ -440,6 +447,8 @@ public static final class GenerateOperation implements AutoCloseable { // block for converter setup converterSetup = clinit.createScope(); + reportUnknown(cc.getMethodCreator(C_REPORT_UNKNOWN)); + // 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); @@ -809,8 +818,7 @@ public void run() { // generate sweep for clinit configSweepLoop(siParserBody, clinit, clinitConfig); - clinit.invokeStaticMethod(CD_UNKNOWN_PROPERTIES, - clinit.readStaticField(FieldDescriptor.of(cc.getClassName(), "unused", List.class))); + clinit.invokeStaticMethod(CD_UNKNOWN_PROPERTIES, clinit.readStaticField(C_UNKNOWN)); if (devMode) { configSweepLoop(siParserBody, readConfig, runTimeConfig); @@ -818,8 +826,7 @@ public void run() { // generate sweep for run time configSweepLoop(rtParserBody, readConfig, runTimeConfig); - readConfig.invokeStaticMethod(CD_UNKNOWN_PROPERTIES_RT, - readConfig.readStaticField(FieldDescriptor.of(cc.getClassName(), "unusedRuntime", List.class))); + readConfig.invokeStaticMethod(CD_UNKNOWN_PROPERTIES_RT, readConfig.readStaticField(C_UNKNOWN_RUNTIME)); if (bootstrapConfigSetupNeeded()) { // generate sweep for bootstrap config @@ -1657,16 +1664,56 @@ private FieldDescriptor getOrCreateConverterInstance(Field field, ConverterType return fd; } - private void reportUnknown(BytecodeCreator methodCreator, ResultHandle unusedProperty) { - ResultHandle unused = methodCreator.readStaticField(C_UNUSED); - methodCreator.invokeVirtualMethod(AL_ADD, unused, - methodCreator.invokeVirtualMethod(NI_GET_NAME, unusedProperty)); + private void reportUnknown(final MethodCreator mc) { + mc.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + + ResultHandle unknownProperty = mc.getMethodParam(0); + + // Ignore all build property names. This is to ignore any properties mapped with @ConfigMapping, because + // these do not fall into the ignore ConfigPattern. + for (String buildTimeProperty : allBuildTimeValues.keySet()) { + ResultHandle equalsResult = mc.invokeVirtualMethod( + MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class), unknownProperty, + mc.load(buildTimeProperty)); + mc.ifTrue(equalsResult).trueBranch().returnValue(null); + } + + ResultHandle unknown = mc.getMethodParam(1); + + // Ignore recorded properties. If properties are present here, it means that they were recorded at build + // time as being mapped in a @ConfigMapping + ResultHandle buildTimeRunTimeSource = mc.readStaticField(C_BUILD_TIME_CONFIG_SOURCE); + ResultHandle buildTimeRunTimeValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeSource, + unknownProperty); + mc.ifNotNull(buildTimeRunTimeValue).trueBranch().returnValue(null); + + ResultHandle buildTimeRunTimeDefaultsSource = mc.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE); + ResultHandle buildTimeRunTimeDefaultValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeDefaultsSource, + unknownProperty); + mc.ifNotNull(buildTimeRunTimeDefaultValue).trueBranch().returnValue(null); + + ResultHandle runtimeSpecifiedSource = mc.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE); + ResultHandle runtimeSpecifiedValue = mc.invokeInterfaceMethod(CS_GET_VALUE, runtimeSpecifiedSource, + unknownProperty); + mc.ifNotNull(runtimeSpecifiedValue).trueBranch().returnValue(null); + + // Add the property as unknown only if all checks fail + mc.invokeVirtualMethod(AL_ADD, unknown, unknownProperty); + + mc.returnValue(null); + mc.close(); + } + + private void reportUnknown(BytecodeCreator bc, ResultHandle unknownProperty) { + ResultHandle property = bc.invokeVirtualMethod(NI_GET_NAME, unknownProperty); + ResultHandle unknown = bc.readStaticField(C_UNKNOWN); + bc.invokeStaticMethod(C_REPORT_UNKNOWN, property, unknown); } - private void reportUnknownRuntime(BytecodeCreator methodCreator, ResultHandle unusedProperty) { - ResultHandle unused = methodCreator.readStaticField(C_UNUSED_RUNTIME); - methodCreator.invokeVirtualMethod(AL_ADD, unused, - methodCreator.invokeVirtualMethod(NI_GET_NAME, unusedProperty)); + private void reportUnknownRuntime(BytecodeCreator bc, ResultHandle unknownProperty) { + ResultHandle property = bc.invokeVirtualMethod(NI_GET_NAME, unknownProperty); + ResultHandle unknown = bc.readStaticField(C_UNKNOWN_RUNTIME); + bc.invokeStaticMethod(C_REPORT_UNKNOWN, property, unknown); } public void close() { 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 5cd2005e2b343f..d1cec417c43699 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 @@ -3,8 +3,10 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.function.Consumer; @@ -17,6 +19,11 @@ import io.quarkus.deployment.configuration.matching.Container; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.util.ClassPathUtils; +import io.smallrye.config.ConfigMappingInterface.LeafProperty; +import io.smallrye.config.ConfigMappingInterface.PrimitiveProperty; +import io.smallrye.config.ConfigMappingInterface.Property; +import io.smallrye.config.ConfigMappings; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; public class ConfigDescriptionBuildStep { @@ -36,6 +43,9 @@ List createConfigDescriptions( processConfig(config.getReadResult().getBuildTimeRunTimePatternMap(), ret, javadoc); processConfig(config.getReadResult().getBootstrapPatternMap(), ret, javadoc); processConfig(config.getReadResult().getRunTimePatternMap(), ret, javadoc); + processMappings(config.getReadResult().getBuildTimeMappings(), ret, javadoc); + processMappings(config.getReadResult().getBuildTimeRunTimeMappings(), ret, javadoc); + processMappings(config.getReadResult().getRunTimeMappings(), ret, javadoc); return ret; } @@ -70,11 +80,42 @@ public void accept(Container node) { } } String javadocKey = field.getDeclaringClass().getName().replace('$', '.') + '.' + field.getName(); - ret.add(new ConfigDescriptionBuildItem("quarkus." + node.getPropertyName(), - node.findEnclosingClass().getConfigurationClass(), - defVal, javadoc.getProperty(javadocKey))); + ret.add(new ConfigDescriptionBuildItem("quarkus." + node.getPropertyName(), defVal, + javadoc.getProperty(javadocKey))); } }); } + private void processMappings(List mappings, List descriptionBuildItems, + Properties javaDocProperties) { + for (ConfigClassWithPrefix mapping : mappings) { + Map properties = ConfigMappings.getProperties(mapping); + for (Map.Entry entry : properties.entrySet()) { + String propertyName = entry.getKey(); + Property property = entry.getValue(); + Method method = property.getMethod(); + + String defaultValue = null; + if (property instanceof PrimitiveProperty) { + PrimitiveProperty primitiveProperty = (PrimitiveProperty) property; + if (primitiveProperty.hasDefaultValue()) { + defaultValue = primitiveProperty.getDefaultValue(); + } else if (primitiveProperty.getPrimitiveType() == boolean.class) { + defaultValue = "false"; + } else if (primitiveProperty.getPrimitiveType() != char.class) { + defaultValue = "0"; + } + } else if (property instanceof LeafProperty) { + LeafProperty leafProperty = (LeafProperty) property; + if (leafProperty.hasDefaultValue()) { + defaultValue = leafProperty.getDefaultValue(); + } + } + + String javadocKey = method.getDeclaringClass().getName().replace('$', '.') + '.' + method.getName(); + descriptionBuildItems.add( + new ConfigDescriptionBuildItem(propertyName, defaultValue, javaDocProperties.getProperty(javadocKey))); + } + } + } } 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 76baaac11caab7..5b234b593e6a3b 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 @@ -31,6 +31,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalBootstrapConfigSourceProviderBuildItem; import io.quarkus.deployment.builditem.AdditionalStaticInitConfigSourceProviderBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; @@ -43,6 +44,7 @@ import io.quarkus.deployment.builditem.SuppressNonRuntimeConfigChangedWarningBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.deployment.configuration.ConfigMappingUtils; import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.deployment.configuration.definition.ClassDefinition; import io.quarkus.deployment.configuration.definition.RootDefinition; @@ -78,6 +80,25 @@ void staticInitSources( PropertiesLocationConfigSourceFactory.class.getName())); } + @BuildStep + void extensionMappings(ConfigurationBuildItem configItem, + CombinedIndexBuildItem combinedIndex, + BuildProducer generatedClasses, + BuildProducer reflectiveClasses, + BuildProducer configClasses) { + List buildTimeRunTimeMappings = configItem.getReadResult().getBuildTimeRunTimeMappings(); + for (ConfigClassWithPrefix buildTimeRunTimeMapping : buildTimeRunTimeMappings) { + ConfigMappingUtils.processConfigClass(buildTimeRunTimeMapping.getKlass(), buildTimeRunTimeMapping.getPrefix(), true, + combinedIndex, false, generatedClasses, reflectiveClasses, configClasses); + } + + final List runTimeMappings = configItem.getReadResult().getRunTimeMappings(); + for (ConfigClassWithPrefix runTimeMapping : runTimeMappings) { + ConfigMappingUtils.processConfigClass(runTimeMapping.getKlass(), runTimeMapping.getPrefix(), true, combinedIndex, + false, generatedClasses, reflectiveClasses, configClasses); + } + } + /** * Generate the Config class that instantiates MP Config and holds all the config objects */ 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 023756d3ad6124..31302719a27ac6 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 @@ -54,6 +54,7 @@ final public class Constants { public static final String ANNOTATION_CONFIG_ITEM = "io.quarkus.runtime.annotations.ConfigItem"; public static final String ANNOTATION_BUILD_STEP = "io.quarkus.deployment.annotations.BuildStep"; public static final String ANNOTATION_CONFIG_ROOT = "io.quarkus.runtime.annotations.ConfigRoot"; + public static final String ANNOTATION_CONFIG_MAPPING = "io.smallrye.config.ConfigMapping"; public static final String ANNOTATION_DEFAULT_CONVERTER = "io.quarkus.runtime.annotations.DefaultConverter"; public static final String ANNOTATION_CONVERT_WITH = "io.quarkus.runtime.annotations.ConvertWith"; public static final String ANNOTATION_CONFIG_GROUP = "io.quarkus.runtime.annotations.ConfigGroup"; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index cc7c803eed7932..351573a63999fb 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -374,6 +374,37 @@ private void recordConfigJavadoc(TypeElement clazz) { default: } } + writeJavadocProperties(clazz, javadocProps); + } + + private void recordMappingJavadoc(TypeElement clazz) { + String className = clazz.getQualifiedName().toString(); + if (!generatedJavaDocs.add(className)) + return; + final Properties javadocProps = new Properties(); + recordMappingJavadoc(clazz, javadocProps); + writeJavadocProperties(clazz, javadocProps); + } + + private void recordMappingJavadoc(final TypeElement clazz, final Properties javadocProps) { + String className = clazz.getQualifiedName().toString(); + for (Element e : clazz.getEnclosedElements()) { + switch (e.getKind()) { + case INTERFACE: { + recordMappingJavadoc(((TypeElement) e), javadocProps); + break; + } + + case METHOD: { + processMethodConfigMapping((ExecutableElement) e, javadocProps, className); + break; + } + default: + } + } + } + + private void writeJavadocProperties(final TypeElement clazz, final Properties javadocProps) { if (javadocProps.isEmpty()) return; final PackageElement pkg = processingEnv.getElementUtils().getPackageOf(clazz); @@ -411,6 +442,11 @@ private void processMethodConfigItem(ExecutableElement method, Properties javado javadocProps.put(className + Constants.DOT + buf.toString(), docComment); } + private void processMethodConfigMapping(ExecutableElement method, Properties javadocProps, String className) { + final String docComment = getRequiredJavadoc(method); + javadocProps.put(className + Constants.DOT + method.getSimpleName().toString(), docComment); + } + private void processConfigGroup(RoundEnvironment roundEnv, TypeElement annotation) { final Set groupClassNames = new HashSet<>(); for (TypeElement i : typesIn(roundEnv.getElementsAnnotatedWith(annotation))) { @@ -442,8 +478,12 @@ private void processConfigRoot(RoundEnvironment roundEnv, TypeElement annotation final String binaryName = processingEnv.getElementUtils().getBinaryName(clazz).toString(); if (rootClassNames.add(binaryName)) { // new class - recordConfigJavadoc(clazz); - generateAccessor(clazz); + if (isAnnotationPresent(clazz, Constants.ANNOTATION_CONFIG_MAPPING)) { + recordMappingJavadoc(clazz); + } else if (isAnnotationPresent(clazz, Constants.ANNOTATION_CONFIG_ROOT)) { + recordConfigJavadoc(clazz); + generateAccessor(clazz); + } final StringBuilder rbn = getRelativeBinaryName(clazz, new StringBuilder()); try { final FileObject itemResource = processingEnv.getFiler().createResource( @@ -628,7 +668,8 @@ private String getRequiredJavadoc(Element e) { String docComment = processingEnv.getElementUtils().getDocComment(e); if (docComment == null) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to find javadoc for config item " + e, e); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Unable to find javadoc for config item " + e.getEnclosingElement() + " " + e, e); return ""; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index 97133943a78903..2e15c17409873d 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -40,6 +40,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -113,7 +114,8 @@ private List recursivelyFindConfigItems(Element element, String r List configDocItems = new ArrayList<>(); TypeElement asTypeElement = (TypeElement) element; TypeMirror superType = asTypeElement.getSuperclass(); - if (superType.getKind() != TypeKind.NONE) { + + if (superType.getKind() != TypeKind.NONE && !superType.toString().equals(Object.class.getName())) { String key = superType.toString(); String rawConfigItems = allConfigurationGroups.get(key); if (rawConfigItems == null) { @@ -129,11 +131,10 @@ private List recursivelyFindConfigItems(Element element, String r } configDocItems.addAll(superTypeConfigItems); - } for (Element enclosedElement : element.getEnclosedElements()) { - if (!enclosedElement.getKind().isField()) { + if (!enclosedElement.getKind().isField() && !enclosedElement.getKind().equals(ElementKind.METHOD)) { continue; } @@ -237,8 +238,23 @@ private List recursivelyFindConfigItems(Element element, String r boolean list = false; boolean optional = false; if (!typeMirror.getKind().isPrimitive()) { - DeclaredType declaredType = (DeclaredType) typeMirror; - TypeElement typeElement = (TypeElement) declaredType.asElement(); + TypeElement typeElement; + DeclaredType declaredType; + if (typeMirror instanceof DeclaredType) { + declaredType = (DeclaredType) typeMirror; + typeElement = (TypeElement) declaredType.asElement(); + } else if (typeMirror instanceof ExecutableType) { + ExecutableType executableType = (ExecutableType) typeMirror; + TypeMirror returnType = executableType.getReturnType(); + if (returnType.getKind().isPrimitive()) { + continue; + } + declaredType = ((DeclaredType) returnType); + typeElement = (TypeElement) declaredType.asElement(); + } else { + continue; + } + Name qualifiedName = typeElement.getQualifiedName(); optional = qualifiedName.toString().startsWith(Optional.class.getName()) || qualifiedName.contentEquals(Map.class.getName()); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java index f0cf97b161be17..3d7684a0ed05a7 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java @@ -50,7 +50,7 @@ public ConfigDocItemScanner() { public void addConfigGroups(TypeElement configGroup) { String configGroupName = configGroup.getQualifiedName().toString(); if (configGroupName.startsWith(IO_QUARKUS_TEST_EXTENSION_PACKAGE)) { - return; + //return; } configGroupsToTypeElement.put(configGroupName, configGroup); @@ -61,7 +61,7 @@ public void addConfigGroups(TypeElement configGroup) { */ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { if (pkg.toString().startsWith(IO_QUARKUS_TEST_EXTENSION_PACKAGE)) { - return; + //return; } ConfigPhase configPhase = ConfigPhase.BUILD_TIME; @@ -82,7 +82,21 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { } } - if (name.isEmpty()) { + String prefix = null; + for (AnnotationMirror mirror : clazz.getAnnotationMirrors()) { + if (mirror.getAnnotationType().toString().equals(Constants.ANNOTATION_CONFIG_MAPPING)) { + for (Entry entry : mirror.getElementValues() + .entrySet()) { + if ("prefix()".equals(entry.getKey().toString())) { + prefix = entry.getValue().getValue().toString(); + } + } + } + } + + if (prefix != null) { + name = prefix; + } else if (name.isEmpty()) { name = deriveConfigRootName(clazz.getSimpleName().toString(), configPhase); } else if (name.endsWith(Constants.DOT + Constants.PARENT)) { // take into account the root case which would contain characters that can't be used to create the final file diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index c29db610ab48f4..807e6137ee4ce5 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -211,7 +211,6 @@ org.wildfly.common:wildfly-common io.smallrye.common:smallrye-common-io - io.smallrye:smallrye-config diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java index 7c2aa916918cdf..76834fc87d92e5 100644 --- a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.BooleanSupplier; @@ -39,10 +40,12 @@ import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; +import io.quarkus.arc.deployment.ConfigPropertyBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalStaticInitConfigSourceProviderBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LogHandlerBuildItem; @@ -66,6 +69,8 @@ import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; import io.quarkus.extest.runtime.config.TestBuildTimeConfig; import io.quarkus.extest.runtime.config.TestConfigRoot; +import io.quarkus.extest.runtime.config.TestMappingBuildTime; +import io.quarkus.extest.runtime.config.TestMappingBuildTimeRunTime; import io.quarkus.extest.runtime.config.TestRunTimeConfig; import io.quarkus.extest.runtime.config.XmlConfig; import io.quarkus.extest.runtime.logging.AdditionalLogHandlerValueFactory; @@ -340,6 +345,48 @@ void checkConfig() { } } + @BuildStep + void configMapping(BuildProducer configProperties, + ConfigurationBuildItem configItem, + TestMappingBuildTime testMappingBuildTime, + TestMappingBuildTimeRunTime testMappingBuildTimeRunTime) { + Map buildTimeValues = configItem.getReadResult().getAllBuildTimeValues(); + Map buildTimeRunTimeValues = configItem.getReadResult().getBuildTimeRunTimeVisibleValues(); + + if (!testMappingBuildTime.value().equals("value") + || !buildTimeValues.getOrDefault("quarkus.mapping.bt.value", "").equals("value")) { + throw new IllegalStateException(); + } + + if (!testMappingBuildTime.group().value().equals("value") + || !buildTimeValues.getOrDefault("quarkus.mapping.bt.group.value", "").equals("value")) { + throw new IllegalStateException(); + } + + if (testMappingBuildTime.missing().isPresent()) { + throw new IllegalStateException(); + } + + if (testMappingBuildTime.present().isEmpty() || !testMappingBuildTime.present().get().value().equals("present") + || !buildTimeValues.getOrDefault("quarkus.mapping.bt.present.value", "").equals("present")) { + throw new IllegalStateException(); + } + + if (testMappingBuildTime.groups().isEmpty() + || !buildTimeValues.getOrDefault("quarkus.mapping.bt.groups[0].value", "").equals("first") + || !buildTimeValues.getOrDefault("quarkus.mapping.bt.groups[1].value", "").equals("second")) + + if (!testMappingBuildTimeRunTime.value().equals("value") + || !buildTimeRunTimeValues.getOrDefault("quarkus.mapping.btrt.value", "").equals("value")) { + throw new IllegalStateException(); + } + + if (!testMappingBuildTimeRunTime.group().value().equals("value") + || !buildTimeRunTimeValues.getOrDefault("quarkus.mapping.btrt.group.value", "").equals("value")) { + throw new IllegalStateException(); + } + } + /** * Collect the beans with our custom bean defining annotation and configure them with the runtime config * diff --git a/core/test-extension/deployment/src/main/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties index ba740ffec768cb..b79781807287f5 100644 --- a/core/test-extension/deployment/src/main/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -148,3 +148,16 @@ quarkus.http.ssl.port=4443 ### Do not record env values in build time bt.do.not.record=properties + +### mappings +quarkus.mapping.bt.value=value +quarkus.mapping.bt.group.value=value +quarkus.mapping.bt.present.value=present +quarkus.mapping.bt.groups[0].value=first +quarkus.mapping.bt.groups[1].value=second + +quarkus.mapping.btrt.value=value +quarkus.mapping.btrt.group.value=value + +quarkus.mapping.rt.value=value +quarkus.mapping.rt.group.value=value diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/config/BuildTimeConfigTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/config/BuildTimeConfigTest.java index 6eddbc3541a007..4bfc5b396c4518 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/config/BuildTimeConfigTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/config/BuildTimeConfigTest.java @@ -9,6 +9,8 @@ import java.nio.file.Paths; import org.eclipse.microprofile.config.ConfigProvider; +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; @@ -30,6 +32,8 @@ public class BuildTimeConfigTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)) + .withConfigurationResource("application.properties") .overrideConfigKey("test-map-config", testFile.getAbsolutePath()); /** diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfigMappingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfigMappingTest.java new file mode 100644 index 00000000000000..d67d0f2be9fc5b --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfigMappingTest.java @@ -0,0 +1,40 @@ +package io.quarkus.extest; + +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.extest.runtime.config.TestMappingBuildTimeRunTime; +import io.quarkus.extest.runtime.config.TestMappingRunTime; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application.properties")); + + @Inject + SmallRyeConfig config; + + @Inject + TestMappingBuildTimeRunTime mappingBuildTimeRunTime; + @Inject + TestMappingRunTime mappingRunTime; + + @Test + void mappingBuildTimeRunTime() { + assertEquals("value", mappingBuildTimeRunTime.value()); + } + + @Test + void mappingRunTime() { + assertEquals("value", mappingBuildTimeRunTime.value()); + } +} diff --git a/core/test-extension/deployment/src/test/resources/build-time-config-test.properties b/core/test-extension/deployment/src/test/resources/build-time-config-test.properties deleted file mode 100644 index a07978c99967c9..00000000000000 --- a/core/test-extension/deployment/src/test/resources/build-time-config-test.properties +++ /dev/null @@ -1,137 +0,0 @@ -# Log settings -# log level in lower case for testing -quarkus.log.level=info -quarkus.log.file.enable=true -quarkus.log.file.level=INFO -quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n - -# Resource path to DSAPublicKey base64 encoded bytes -quarkus.root.dsa-key-location=/DSAPublicKey.encoded - -# Have the TestProcessor validate the build time configuration below -quarkus.root.validate-build-config=true - - -### Configuration settings for the TestBuildTimeConfig config root -quarkus.bt.bt-string-opt=btStringOptValue -quarkus.bt.bt-sbv=StringBasedValue -# This is not set so that we should get the @ConfigItem defaultValue -#quarkus.bt.bt-sbv-with-default=StringBasedValue -quarkus.bt.all-values.oov=configPart1+configPart2 -quarkus.bt.all-values.ovo=configPart1+configPart2 -# This is not set so that we should get the @ConfigItem defaultValue -#quarkus.bt.bt-oov-with-default=ObjectOfValue -quarkus.bt.all-values.long-primitive=1234567891 -quarkus.bt.all-values.double-primitive=3.1415926535897932384 -quarkus.bt.all-values.long-value=1234567892 -quarkus.bt.all-values.opt-long-value=1234567893 -quarkus.bt.all-values.opt-double-value=3.1415926535897932384 -quarkus.bt.all-values.optional-long-value=1234567894 -quarkus.bt.all-values.nested-config-map.key1.nested-value=value1 -quarkus.bt.all-values.nested-config-map.key1.oov=value1.1+value1.2 -quarkus.bt.all-values.nested-config-map.key2.nested-value=value2 -quarkus.bt.all-values.nested-config-map.key2.oov=value2.1+value2.2 -quarkus.bt.all-values.string-list=value1,value2 -quarkus.bt.all-values.long-list=1,2,3 - -### Duplicate settings for the TestBuildAndRunTimeConfig. May be able to drop if ConfigRoot inheritance is added -quarkus.btrt.bt-string-opt=btStringOptValue -quarkus.btrt.bt-sbv=StringBasedValue -quarkus.btrt.all-values.oov=configPart1+configPart2 -quarkus.btrt.all-values.ovo=configPart1+configPart2 -quarkus.btrt.all-values.long-primitive=1234567891 -quarkus.btrt.all-values.double-primitive=3.1415926535897932384 -quarkus.btrt.all-values.long-value=1234567892 -quarkus.btrt.all-values.opt-long-value=1234567893 -quarkus.btrt.all-values.opt-double-value=3.1415926535897932384 -quarkus.btrt.all-values.optional-long-value=1234567894 -quarkus.btrt.all-values.nested-config-map.key1.nested-value=value1 -quarkus.btrt.all-values.nested-config-map.key1.oov=value1.1+value1.2 -quarkus.btrt.all-values.nested-config-map.key2.nested-value=value2 -quarkus.btrt.all-values.nested-config-map.key2.oov=value2.1+value2.2 -quarkus.btrt.all-values.string-list=value1,value2 -quarkus.btrt.all-values.long-list=1,2,3 -# The expansion value is not available in runtime so we need to set it directly. -quarkus.btrt.all-values.expanded-default=1234 - -### Configuration settings for the TestRunTimeConfig config root -quarkus.rt.rt-string-opt=rtStringOptValue -quarkus.rt.rt-string-opt-with-default=rtStringOptWithDefaultValue -quarkus.rt.all-values.oov=configPart1+configPart2 -quarkus.rt.all-values.ovo=configPart1+configPart2 -quarkus.rt.all-values.long-primitive=12345678911 -quarkus.rt.all-values.double-primitive=3.1415926535897932384 -quarkus.rt.all-values.long-value=12345678921 -quarkus.rt.all-values.opt-long-value=12345678931 -quarkus.rt.all-values.opt-double-value=3.1415926535897932384 -quarkus.rt.all-values.optional-long-value=12345678941 -quarkus.rt.all-values.nested-config-map.key1.nested-value=value1 -quarkus.rt.all-values.nested-config-map.key1.oov=value1.1+value1.2 -quarkus.rt.all-values.nested-config-map.key2.nested-value=value2 -quarkus.rt.all-values.nested-config-map.key2.oov=value2.1+value2.2 -quarkus.rt.all-values.string-list=value1,value2 -quarkus.rt.all-values.long-list=1,2,3 -# A nested map of properties -quarkus.rt.all-values.string-map.key1=value1 -quarkus.rt.all-values.string-map.key2=value2 -quarkus.rt.all-values.string-map.key3=value3 -# And list form -quarkus.rt.all-values.string-list-map.key1=value1,value2,value3 -quarkus.rt.all-values.string-list-map.key2=value4,value5 -quarkus.rt.all-values.string-list-map.key3=value6 -# A root map of properties -quarkus.rt.string-map.key1=value1 -quarkus.rt.string-map.key2=value2 -quarkus.rt.string-map.key3=value3 -# And list form -quarkus.rt.string-list-map.key1=value1 -quarkus.rt.string-list-map.key2=value2,value3 -quarkus.rt.string-list-map.key3=value4,value5,value6 - -### run time configuration using enhanced converters -quarkus.rt.my-enum=enum-two -quarkus.rt.my-enums=enum-one,enum-two -quarkus.rt.my-optional-enums=optional -quarkus.rt.no-hyphenate-first-enum=ENUM_ONE -quarkus.rt.no-hyphenate-second-enum=Enum_Two -quarkus.rt.primitive-boolean=YES -quarkus.rt.object-boolean=NO -quarkus.rt.primitive-integer=two -quarkus.rt.object-integer=nine -quarkus.rt.one-to-nine=one,two,three,four,five,six,seven,eight,nine -quarkus.rt.map-of-numbers.key1=one -quarkus.rt.map-of-numbers.key2=two - -### map configurations -quarkus.rt.leaf-map.key.first=first-key-value -quarkus.rt.leaf-map.key.second=second-key-value -quarkus.rt.config-group-map.key.group.nested-value=value -quarkus.rt.config-group-map.key.group.oov=value2.1+value2.2 - -### build time and run time configuration using enhanced converters -quarkus.btrt.map-of-numbers.key1=one -quarkus.btrt.map-of-numbers.key2=two -quarkus.btrt.my-enum=optional -quarkus.btrt.my-enums=optional,enum-one,enum-two - -### anonymous root property -quarkus.test-property=foo - -### map of map of strings -quarkus.rt.map-map.outer-key.inner-key=1234 -quarkus.btrt.map-map.outer-key.inner-key=1234 -quarkus.bt.map-map.outer-key.inner-key=1234 - -# Test config root with "RuntimeConfig" suffix -quarkus.foo.bar=huhu - -### named map with profiles -quarkus.btrt.map-map.main-profile.property=1234 -%test.quarkus.btrt.map-map.test-profile.property=5678 - -### ordinal and default values source -config_ordinal=1000 -my.prop=1234 -%prod.my.prop=1234 -%dev.my.prop=5678 -%test.my.prop=1234 \ No newline at end of file diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTime.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTime.java new file mode 100644 index 00000000000000..5872a54f6d0a23 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTime.java @@ -0,0 +1,44 @@ +package io.quarkus.extest.runtime.config; + +import java.util.List; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "quarkus.mapping.bt") +@ConfigRoot(phase = ConfigPhase.BUILD_TIME) +public interface TestMappingBuildTime { + /** + * A String value. + */ + String value(); + + /** + * A nested Group. + */ + Group group(); + + /** + * An Optional missing Group. + */ + Optional missing(); + + /** + * An Optional present Group. + */ + Optional present(); + + /** + * A List of Groups. + */ + List groups(); + + interface Group { + /** + * A Group value. + */ + String value(); + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTimeRunTime.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTimeRunTime.java new file mode 100644 index 00000000000000..781a017516036f --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingBuildTimeRunTime.java @@ -0,0 +1,26 @@ +package io.quarkus.extest.runtime.config; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "quarkus.mapping.btrt") +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public interface TestMappingBuildTimeRunTime { + /** + * A String value + */ + String value(); + + /** + * A nested Group. + */ + Group group(); + + interface Group { + /** + * A Group value. + */ + String value(); + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingRunTime.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingRunTime.java new file mode 100644 index 00000000000000..39d3fdabccd780 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestMappingRunTime.java @@ -0,0 +1,26 @@ +package io.quarkus.extest.runtime.config; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "quarkus.mapping.rt") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public interface TestMappingRunTime { + /** + * A String value. + */ + String value(); + + /** + * A nested Group. + */ + Group group(); + + interface Group { + /** + * A Group value. + */ + String value(); + } +}