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 779b2222392013..4b0384fc1678cb 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 @@ -460,7 +460,7 @@ private void recordMappingJavadoc(final TypeElement clazz, final Properties java for (Element e : clazz.getEnclosedElements()) { switch (e.getKind()) { case INTERFACE: { - recordMappingJavadoc(((TypeElement) e), javadocProps); + recordMappingJavadoc(((TypeElement) e)); break; } 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 f7b9447c448d6d..16a314905dacf0 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 @@ -19,6 +19,7 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenateEnumValue; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.stringifyType; +import static javax.lang.model.element.Modifier.ABSTRACT; import java.io.IOException; import java.time.Duration; @@ -86,11 +87,11 @@ public ConfigDoItemFinder(Set configRoots, * */ ScannedConfigDocsItemHolder findInMemoryConfigurationItems() throws IOException { - for (Map.Entry entry : configGroupQualifiedNameToTypeElementMap.entrySet()) { ConfigPhase buildTime = ConfigPhase.BUILD_TIME; - final List configDocItems = recursivelyFindConfigItems( - entry.getValue(), EMPTY, EMPTY, buildTime, false, false, 1, false); + TypeElement element = entry.getValue(); + final List configDocItems = recursivelyFindConfigItems(element, EMPTY, EMPTY, buildTime, false, 1, + false); allConfigurationGroups.put(entry.getKey(), OBJECT_MAPPER.writeValueAsString(configDocItems)); } @@ -99,9 +100,8 @@ ScannedConfigDocsItemHolder findInMemoryConfigurationItems() throws IOException final TypeElement element = configRootInfo.getClazz(); String rootName = configRootInfo.getName(); ConfigPhase configPhase = configRootInfo.getConfigPhase(); - boolean isMapping = configRootInfo.isMapping(); final List configDocItems = recursivelyFindConfigItems(element, rootName, rootName, configPhase, - isMapping, false, sectionLevel, true); + false, sectionLevel, true); holder.addConfigRootItems(configRootInfo, configDocItems); allConfigurationRoots.put(configRootInfo.getClazz().toString(), OBJECT_MAPPER.writeValueAsString(configDocItems)); } @@ -113,8 +113,7 @@ ScannedConfigDocsItemHolder findInMemoryConfigurationItems() throws IOException * Recursively find config item found in a config root or config group given as {@link Element} */ private List recursivelyFindConfigItems(Element element, String rootName, String parentName, - ConfigPhase configPhase, boolean isMapping, boolean withinAMap, int sectionLevel, - boolean generateSeparateConfigGroupDocsFiles) + ConfigPhase configPhase, boolean withinAMap, int sectionLevel, boolean generateSeparateConfigGroupDocsFiles) throws JsonProcessingException { List configDocItems = new ArrayList<>(); TypeElement asTypeElement = (TypeElement) element; @@ -130,7 +129,7 @@ private List recursivelyFindConfigItems(Element element, String r if (rawConfigItems == null) { // element not yet scanned Element superElement = ((DeclaredType) superType).asElement(); superTypeConfigItems = recursivelyFindConfigItems(superElement, rootName, parentName, - configPhase, isMapping, withinAMap, sectionLevel, generateSeparateConfigGroupDocsFiles); + configPhase, withinAMap, sectionLevel, generateSeparateConfigGroupDocsFiles); } else { superTypeConfigItems = OBJECT_MAPPER.readValue(rawConfigItems, LIST_OF_CONFIG_ITEMS_TYPE_REF); } @@ -139,7 +138,9 @@ private List recursivelyFindConfigItems(Element element, String r } for (Element enclosedElement : element.getEnclosedElements()) { - if (!enclosedElement.getKind().isField() && (!isMapping || !enclosedElement.getKind().equals(ElementKind.METHOD))) { + shouldProcessElement(enclosedElement); + + if (!shouldProcessElement(enclosedElement)) { continue; } @@ -221,15 +222,13 @@ private List recursivelyFindConfigItems(Element element, String r } // Mappings - if (isMapping) { - for (Map.Entry entry : annotationMirror - .getElementValues().entrySet()) { - Object value = entry.getValue().getValue(); - if (annotationName.equals(ANNOTATION_CONFIG_WITH_NAME)) { - name = parentName + DOT + value; - } else if (annotationName.equals(ANNOTATION_CONFIG_WITH_DEFAULT)) { - defaultValue = value.toString(); - } + for (Map.Entry entry : annotationMirror + .getElementValues().entrySet()) { + Object value = entry.getValue().getValue(); + if (annotationName.equals(ANNOTATION_CONFIG_WITH_NAME)) { + name = parentName + DOT + value; + } else if (annotationName.equals(ANNOTATION_CONFIG_WITH_DEFAULT)) { + defaultValue = value.toString(); } } } @@ -254,7 +253,7 @@ private List recursivelyFindConfigItems(Element element, String r if (isConfigGroup(type)) { List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type, - configSection, isMapping, withinAMap, generateSeparateConfigGroupDocsFiles); + configSection, withinAMap, generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); } else { final ConfigDocKey configDocKey = new ConfigDocKey(); @@ -278,7 +277,7 @@ private List recursivelyFindConfigItems(Element element, String r if (isConfigGroup(type)) { name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type, - configSection, isMapping, true, generateSeparateConfigGroupDocsFiles); + configSection, true, generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); continue; } else { @@ -304,8 +303,7 @@ private List recursivelyFindConfigItems(Element element, String r } configSection.setOptional(true); List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, - typeInString, configSection, isMapping, withinAMap, - generateSeparateConfigGroupDocsFiles); + typeInString, configSection, withinAMap, generateSeparateConfigGroupDocsFiles); DocGeneratorUtil.appendConfigItemsIntoExistingOnes(configDocItems, groupConfigItems); continue; } else if ((typeInString.startsWith(List.class.getName()) @@ -390,6 +388,20 @@ private boolean isConfigGroup(String type) { return configGroupQualifiedNameToTypeElementMap.containsKey(type) || allConfigurationGroups.hasKey(type); } + private boolean shouldProcessElement(final Element enclosedElement) { + if (enclosedElement.getKind().isField()) { + return true; + } + + // A ConfigMapping method + if (enclosedElement.getKind().equals(ElementKind.METHOD)) { + Element enclosingElement = enclosedElement.getEnclosingElement(); + return enclosingElement.getModifiers().contains(ABSTRACT) && enclosedElement.getModifiers().contains(ABSTRACT); + } + + return false; + } + private String simpleTypeToString(TypeMirror typeMirror) { if (typeMirror.getKind().isPrimitive()) { return typeMirror.toString(); @@ -456,8 +468,8 @@ private boolean isDurationType(TypeMirror realTypeMirror) { * */ private List readConfigGroupItems(ConfigPhase configPhase, String topLevelRootName, String parentName, - String configGroup, ConfigDocSection configSection, boolean isMapping, boolean withinAMap, - boolean generateSeparateConfigGroupDocs) throws JsonProcessingException { + String configGroup, ConfigDocSection configSection, boolean withinAMap, boolean generateSeparateConfigGroupDocs) + throws JsonProcessingException { configSection.setConfigGroupType(configGroup); if (configSection.getSectionDetailsTitle() == null) { @@ -475,7 +487,7 @@ private List readConfigGroupItems(ConfigPhase configPhase, String groupConfigItems = OBJECT_MAPPER.readValue(property, LIST_OF_CONFIG_ITEMS_TYPE_REF); } else { TypeElement configGroupTypeElement = configGroupQualifiedNameToTypeElementMap.get(configGroup); - groupConfigItems = recursivelyFindConfigItems(configGroupTypeElement, EMPTY, EMPTY, configPhase, isMapping, + groupConfigItems = recursivelyFindConfigItems(configGroupTypeElement, EMPTY, EMPTY, configPhase, false, 1, generateSeparateConfigGroupDocs); allConfigurationGroups.put(configGroup, OBJECT_MAPPER.writeValueAsString(groupConfigItems)); } 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 fe79f0a4861a89..8ed7d934c8fb1b 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 @@ -66,7 +66,6 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { String prefix = Constants.QUARKUS; ConfigPhase configPhase = ConfigPhase.BUILD_TIME; - boolean isMapping = false; for (AnnotationMirror annotationMirror : clazz.getAnnotationMirrors()) { String annotationName = annotationMirror.getAnnotationType().toString(); @@ -88,7 +87,6 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { for (AnnotationMirror mirror : clazz.getAnnotationMirrors()) { if (mirror.getAnnotationType().toString().equals(Constants.ANNOTATION_CONFIG_MAPPING)) { - isMapping = true; name = Constants.EMPTY; for (Entry entry : mirror.getElementValues() .entrySet()) { @@ -113,7 +111,7 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { fileName = name.replace(Constants.DOT, Constants.DASH.charAt(0)) + Constants.ADOC_EXTENSION; } - ConfigRootInfo configRootInfo = new ConfigRootInfo(name, clazz, configPhase, isMapping, fileName); + ConfigRootInfo configRootInfo = new ConfigRootInfo(name, clazz, configPhase, fileName); configRoots.add(configRootInfo); break; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigRootInfo.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigRootInfo.java index 4652502c984a08..786c07fc871863 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigRootInfo.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigRootInfo.java @@ -8,17 +8,16 @@ final public class ConfigRootInfo { private final String name; private final TypeElement clazz; private final ConfigPhase configPhase; - private final boolean mapping; private final String fileName; public ConfigRootInfo( final String name, final TypeElement clazz, - final ConfigPhase configPhase, final boolean mapping, final String fileName) { + final ConfigPhase configPhase, + final String fileName) { this.name = name; this.clazz = clazz; this.configPhase = configPhase; - this.mapping = mapping; this.fileName = fileName; } @@ -35,8 +34,7 @@ public boolean equals(final Object o) { return false; } final ConfigRootInfo that = (ConfigRootInfo) o; - return mapping == that.mapping && - name.equals(that.name) && + return name.equals(that.name) && clazz.equals(that.clazz) && configPhase == that.configPhase && fileName.equals(that.fileName); @@ -44,7 +42,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(name, clazz, configPhase, mapping, fileName); + return Objects.hash(name, clazz, configPhase, fileName); } @Override @@ -53,7 +51,6 @@ public String toString() { "name='" + name + '\'' + ", clazz=" + clazz + ", configPhase=" + configPhase + - ", mapping=" + mapping + ", fileName='" + fileName + '\'' + '}'; } @@ -69,8 +66,4 @@ public TypeElement getClazz() { public ConfigPhase getConfigPhase() { return configPhase; } - - public boolean isMapping() { - return mapping; - } } diff --git a/docs/src/main/asciidoc/platform.adoc b/docs/src/main/asciidoc/platform.adoc index 010426f5fad4c3..4451a70e81c334 100644 --- a/docs/src/main/asciidoc/platform.adoc +++ b/docs/src/main/asciidoc/platform.adoc @@ -111,13 +111,14 @@ Extension developers that want to make their configuration options platform-spec package io.quarkus.deployment.pkg; @ConfigRoot(phase = ConfigPhase.BUILD_TIME) -public class NativeConfig { +@ConfigMapping(prefix = "quarkus") +public interface NativeConfig { /** * The docker image to use to do the image build */ - @ConfigItem(defaultValue = "${platform.quarkus.native.builder-image}") - public String builderImage; + @WithDefault("${platform.quarkus.native.builder-image}") + String builderImage(); } ---- diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 31921e654df326..cf16b15a99d726 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -864,7 +864,7 @@ The types of values that can be injected include: - <> produced by previous build steps - <> to produce items for subsequent build steps -- <> types +- <> types - Template objects for <> WARNING: Objects which are injected into a build step method or its class _must not_ be used outside that method's @@ -1050,122 +1050,19 @@ will result in the creation of the `build.dot` file in the project's root direct [[configuration]] === Configuration -Configuration in Quarkus is based on SmallRye Config, an implementation of the MicroProfile Config specification. -All the standard features of MP-Config are supported; in addition, there are several extensions which are made available -by the SmallRye Config project as well as by Quarkus itself. +Configuration in Quarkus is based on https://smallrye.io/smallrye-config/Main/[SmallRye Config]. All features provided +by https://smallrye.io/smallrye-config/Main/[SmallRye Config] are also available in Quarkus. -The value of these properties is configured in a `application.properties` file that follows the MicroProfile config format. +Extensions must use https://smallrye.io/smallrye-config/Main/config/mappings/[SmallRye Config @ConfigMapping] to map +the configuration required by the Extension. This will allow Quarkus to automatically expose an instance of the mapping +to each configuration phase and generate the configuration documentation. -Configuration of Quarkus extensions is injection-based, using annotations. +==== Config Phases -==== Configuration Keys - -Leaf configuration keys are mapped to non-`private` fields via the `@io.quarkus.runtime.annotations.ConfigItem` annotation. - -NOTE: Though the SmallRye Config project is used for implementation, the standard `@ConfigProperty` annotation does not have the -same semantics that are needed to support configuration within extensions. - -Configuration keys are normally derived from the field names that they are tied to. This is done by de-camel-casing the name and then -joining the segments with hyphens (`-`). Some examples: - -* `bindAddress` becomes `bind-address` -* `keepAliveTime` becomes `keep-alive-time` -* `requestDNSTimeout` becomes `request-dns-timeout` - -The name can also be explicitly specified by giving a `name` attribute to the `@ConfigItem` annotation. - -NOTE: Though it is possible to override the configuration key name using the `name` attribute of `@ConfigItem`, -normally this should only be done in cases where (for example) the configuration key name is the same as a Java keyword. - -==== Configuration Value types - -The type of the field with the `@ConfigItem` annotation determines the conversion that is applied to it. Quarkus -extensions may use the full range of configuration types made available by SmallRye Config, which includes: - -* All primitive types and primitive wrapper types -* `String` -* Any type which has a constructor accepting a single argument of type `String` or `CharSequence` -* Any type which has a static method named `of` which accepts a single argument of type `String` -* Any type which has a static method named `valueOf` or `parse` which accepts a single argument of type `CharSequence` or `String` -* `java.time.Duration` -* `java.util.regex.Pattern` -* `java.nio.file.Path` -* `io.quarkus.runtime.configuration.MemorySize` to represent data sizes -* `java.net.InetSocketAddress`, `java.net.InetAddress` and `org.wildfly.common.net.CidrAddress` -* `java.util.Locale` where the string value is an IETF BCP 47 language tag -* `java.nio.charset.Charset` where the string value is a canonical name or an alias -* `java.time.ZoneId` where the string value is parsed via `java.time.ZoneId.of(String)` -* A `List` or `Optional` of any of the above types -* `OptionalInt`, `OptionalLong`, `OptionalDouble` - -In addition, custom converters may be registered by adding their fully qualified class name in file -`META-INF/services/org.eclipse.microprofile.config.spi.Converter`. - -Though these implicit converters use reflection, Quarkus will automatically ensure that they are loaded at the appropriate time. - -===== Optional Values - -If the configuration type is one of the optional types, then empty values are allowed for the configuration key; otherwise, -specification of an empty value will result in a configuration error which prevents the application from starting. This -is especially relevant to configuration properties of inherently emptiable values such as `List`, `Set`, and `String`. Such -value types will never be empty; in the event of an empty value, an empty `Optional` is always used. - -==== Configuration Default Values - -A configuration item can be marked to have a default value. The default value is used when no matching configuration key -is specified in the configuration. - -Configuration items with a primitive type (such as `int` or `boolean`) implicitly use a default value of `0` or `false`. The -sole exception to this rule is the `char` type which does not have an implicit default value. - -A property with a default value is not implicitly optional. If a non-optional configuration item with a default value -is explicitly specified to have an empty value, the application will report a configuration error and will not start. If -it is desired for a property to have a default value and also be optional, it must have an `Optional` type as described above. - -==== Configuration Groups - -Configuration values are always collected into grouping classes which are marked with the `@io.quarkus.runtime.annotations.ConfigGroup` -annotation. These classes contain a field for each key within its group. In addition, configuration groups can be nested. - -===== Optional Configuration Groups - -A nested configuration group may be wrapped with an `Optional` type. In this case, the group is not populated unless one -or more properties within that group are specified in the configuration. If the group is populated, then any required -properties in the group must also be specified otherwise a configuration error will be reported and the application will -not start. - -==== Configuration Maps - -A `Map` can be used for configuration at any position where a configuration group would be allowed. The key type of such a -map *must* be `String`, and its value may be either a configuration group class or a valid leaf type. The configuration -key segment following the map's key segment will be used as the key for map values. - -[id='configuration-roots'] -==== Configuration Roots - -Configuration roots are configuration groups that appear in the root of the configuration tree. A configuration property's full -name is determined by joining the string `quarkus.` with the hyphenated name of the fields that form the path from the root to the -leaf field. For example, if I define a configuration root group called `ThreadPool`, with a nested group in a field named `sizing` -that in turn contains a field called `minSize`, the final configuration property will be called `quarkus.thread-pool.sizing.min-size`. - -A configuration root's name can be given with the `name` property, or it can be inferred from the class name. If the latter, -then the configuration key will be the class name, minus any `Config` or `Configuration` suffix, broken up by camel-case, -lowercased, and re-joined using hyphens (`-`). - -A configuration root's class name can contain an extra suffix segment for the case where there are configuration -roots for multiple <>. Classes which correspond to the `BUILD_TIME` and `BUILD_AND_RUN_TIME_FIXED` -may end with `BuildTimeConfig` or `BuildTimeConfiguration`, classes which correspond to the `RUN_TIME` phase -may end with `RuntimeConfig`, `RunTimeConfig`, `RuntimeConfiguration` or `RunTimeConfiguration` while classes which correspond -to the `BOOTSTRAP` configuration may end with `BootstrapConfig` or `BootstrapConfiguration`. - -Note: The current implementation is still using injection site to determine the root set, so to avoid migration problems, it -is recommended that the injection site (field or parameter) have the same name as the configuration root class until -this change is complete. - -===== Configuration Root Phases - -Configuration roots are strictly bound by configuration phase, and attempting to access a configuration root from outside its corresponding phase will result in an error. -A configuration root dictates when its contained keys are read from configuration, and when they are available to applications. The phases defined by `io.quarkus.runtime.annotations.ConfigPhase` are as follows: +Configuration mappings are strictly bound by configuration phase, and attempting to access a configuration mapping from +outside its corresponding phase will result in an error. They dictate when its contained keys are read from the +configuration, and when they are available to applications. The phases defined by +`io.quarkus.runtime.annotations.ConfigPhase` are as follows: [cols="<3m,^1,^1,^1,^1,<8",options="header"] |=== @@ -1206,7 +1103,7 @@ A configuration root dictates when its contained keys are read from configuratio |=== -For all cases other than the `BUILD_TIME` case, the configuration root class and all the configuration groups and types contained therein must be located in, or reachable from, the extension's run time artifact. Configuration roots of phase `BUILD_TIME` may be located in or reachable from either of the extension's run time or deployment artifacts. +For all cases other than the `BUILD_TIME` case, the configuration mapping interface and all the configuration groups and types contained therein must be located in, or reachable from, the extension's run time artifact. Configuration mappings of phase `BUILD_TIME` may be located in or reachable from either of the extension's run time or deployment artifacts. IMPORTANT: _Bootstrap_ configuration steps are executed during runtime-init *before* any of other runtime steps. This means that code executed as part of this step cannot access anything that gets initialized in runtime init steps (runtime synthetic CDI beans being one such example). @@ -1214,84 +1111,84 @@ IMPORTANT: _Bootstrap_ configuration steps are executed during runtime-init *bef [source%nowrap,java] ---- -import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.DefaultConverter +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; import java.io.File; import java.util.logging.Level; -@ConfigGroup <1> -public class FileConfig { - - /** - * Enable logging to a file. - */ - @ConfigItem(defaultValue = "true") - boolean enable; - - /** - * The log format. - */ - @ConfigItem(defaultValue = "%d{yyyy-MM-dd HH:mm:ss,SSS} %h %N[%i] %-5p [%c{1.}] (%t) %s%e%n") - String format; - - /** - * The level of logs to be written into the file. - */ - @ConfigItem(defaultValue = "ALL") - Level level; - - /** - * The name of the file in which logs will be written. - */ - @ConfigItem(defaultValue = "application.log") - File path; - -} - /** * Logging configuration. */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME) <2> -public class LogConfiguration { - +@ConfigMapping(prefix = "quarkus.log") // <1> +@ConfigRoot(phase = ConfigPhase.RUN_TIME) // <2> +public interface LogConfiguration { // ... /** * Configuration properties for the logging file handler. */ - FileConfig file; + FileConfig file(); + + @ConfigGroup // <3> + interface FileConfig { + /** + * Enable logging to a file. + */ + @WithDefault("true") + boolean enable(); + + /** + * The log format. + */ + @WithDefault("%d{yyyy-MM-dd HH:mm:ss,SSS} %h %N[%i] %-5p [%c{1.}] (%t) %s%e%n") + String format(); + + /** + * The level of logs to be written into the file. + */ + @WithDefault("ALL") + Level level(); + + /** + * The name of the file in which logs will be written. + */ + @WithDefault("application.log") + File path(); + } } +---- +[source%nowrap,java] +---- public class LoggingProcessor { // ... - /** + /* * Logging configuration. */ - <3> - LogConfiguration config; + LogConfiguration config; // <4> } ---- A configuration property name can be split into segments. For example, a property name like `quarkus.log.file.enable` can be split into the following segments: -* `quarkus` - a namespace claimed by Quarkus which is a prefix for all `@ConfigRoot` classes, -* `log` - a name segment which corresponds to the `LogConfiguration` class annotated with `@ConfigRoot`, +* `quarkus` - a namespace claimed by Quarkus which is a prefix for `@ConfigMapping` interfaces, +* `log` - a name segment which corresponds to the prefix set in the interface annotated with `@ConfigMapping`, * `file` - a name segment which corresponds to the `file` field in this class, * `enabled` - a name segment which corresponds to `enable` field in `FileConfig` class annotated with `@ConfigGroup`. -<1> The `FileConfig` class is annotated with `@ConfigGroup` to indicate that this is an aggregate +<1> The `@ConfigMapping` annotation indicates that the interface is a configuration mapping, in this case one which +corresponds to a `quarkus.log` segment. +<2> The `@ConfigRoot` annotation indicated to which Config phase, the configuration applies to. +<3> The `FileConfig` class is annotated with `@ConfigGroup` to indicate that this is an aggregate configuration object containing a collection of configurable properties, rather than being a simple configuration key type. -<2> The `@ConfigRoot` annotation indicates that this object is a configuration root group, in this case one which -corresponds to a `log` segment. A class name is used to link configuration root group with the segment from a -property name. The `Configuration` part is stripped off from a `LogConfiguration` class name and the remaining `Log` -is lowercased to become a `log`. Since all `@ConfigRoot` annotated classes uses `quarkus` as a prefix, this finally -becomes `quarkus.log` and represents the properties which names begin with `quarkus.log.*`. -<3> Here the `LoggingProcessor` injects a `LogConfiguration` instance automatically by detecting the `@ConfigRoot` +<4> Here the `LoggingProcessor` injects a `LogConfiguration` instance automatically by detecting the `@ConfigRoot` annotation. A corresponding `application.properties` for the above example could be: @@ -1305,100 +1202,159 @@ quarkus.log.file.path=/tmp/debug.log Since `format` is not defined in these properties, the default value from `@ConfigItem` will be used instead. +A configuration mapping name can contain an extra suffix segment for the case where there are configuration +mappings for multiple <>. Classes which correspond to the `BUILD_TIME` and `BUILD_AND_RUN_TIME_FIXED` +may end with `BuildTimeConfig` or `BuildTimeConfiguration`, classes which correspond to the `RUN_TIME` phase +may end with `RuntimeConfig`, `RunTimeConfig`, `RuntimeConfiguration` or `RunTimeConfiguration` while classes which +correspond to the `BOOTSTRAP` configuration may end with `BootstrapConfig` or `BootstrapConfiguration`. -==== Enhanced conversion -You can use enhanced conversion of a config item by using the `@ConvertWith` annotation which accepts a `Converter` class object. -If the annotation is present on a config item, the implicit or custom-built in converter in use will be overridden by the value provided. -To do, see the example below which converts `YES` or `NO` values to `boolean`. -[source%nowrap,java] ----- -@ConfigRoot -public class SomeConfig { - /** - * Config item with enhanced converter - */ - @ConvertWith(YesNoConverter.class) // <1> - @ConfigItem(defaultValue = "NO") - Boolean answer; - +==== Configuration Documentation - public static class YesNoConverter implements Converter { +The configuration is an important part of each extension and therefore needs to be properly documented. Each +configuration property must have a proper Javadoc comment. - public YesNoConverter() {} +While it is handy to have the documentation available when coding, this configuration documentation must also be +available in the extension guides. The Quarkus build automatically generates the configuration documentation based on +the Javadoc comments, but it needs to explicitly included in the extension guide. - @Override - public Boolean convert(String s) { - if (s == null || s.isEmpty()) { - return false; - } +===== Writing the Documentation - switch (s) { - case "YES": - return true; - case "NO": - return false; - } +You can either use standard Javadoc comments or Asciidoc directly as a Javadoc comment. - throw new IllegalArgumentException("Unsupported value " + s + " given"); - } - } -} ----- -<1> Override the default `Boolean` converter and use the provided converter which accepts a `YES` or `NO` config values. +We assume you are familiar with writing Javadoc comments so let's focus on our Asciidoc support. +While standard Javadoc comments are perfectly fine for simple documentation (recommended even), +if you want to include tips, source code extracts, lists... Asciidoc comes in handy. +Here is a typical configuration property commented with Asciidoc: -The corresponding `application.properties` will look like. -[source%nowrap,properties] +[source,java] ---- -quarkus.some.answer=YES +@ConfigMapping(prefix = "quarkus.driver") +@ConfigRoot +interface DriverConfig { + /** + * Class name of the Hibernate ORM dialect. The complete list of bundled dialects is available in the + * https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/dialect/package-summary.html[Hibernate ORM + * JavaDoc]. + *
+ * [NOTE] + * ==== + * Not all the dialects are supported in GraalVM native executables: we currently provide driver extensions for + * PostgreSQL, + * MariaDB, Microsoft SQL Server and H2. + * ==== + * + * @asciidoclet + */ + Optional dialect(); +} ---- -[NOTE] -===== -Enum values (config items) are translated to skewed-case (hyphenated) by default. The table below illustrates an enum name and their canonical equivalence: - -|=== -|Java enum| Canonical equivalent - -|DISCARD -|discard +This is the simple case: you just have to write Asciidoc and mark the comment with the `@asciidoclet` tag. +This tag has two purposes: it is used as a marker for our generation tool, but it is also used by the `javadoc` process +for proper Javadoc generation. -|READ_UNCOMMITTED -|read-uncommitted +[TIP] +==== +Always make the first sentence meaningful and self-contained as it is included in the summary table. +==== -|SIGUSR1 -|sigusr1 +Now let's consider a more complicated example: -|JavaEnum -|java-enum +[source,java] +---- +@ConfigMapping(prefix = "quarkus.datasource") +@ConfigRoot +interface DatasourceConfig { + // @formatter:off + /** + * Name of the file containing the SQL statements to execute when Hibernate ORM starts. + * Its default value differs depending on the Quarkus launch mode: + *
+ * * In dev and test modes, it defaults to `import.sql`. + * Simply add an `import.sql` file in the root of your resources directory, + * and it will be picked up without having to set this property. + * Pass `no-file` to force Hibernate ORM to ignore the SQL import file. + * * In production mode, it defaults to `no-file`. + * It means Hibernate ORM won't try to execute any SQL import file by default. + * Pass an explicit value to force Hibernate ORM to execute the SQL import file. + *
+ * If you need different SQL statements between dev mode, test (`@QuarkusTest`) and in production, use Quarkus + * https://quarkus.io/guides/config#configuration-profiles[configuration profiles facility]. + *
+ * [source,property] + * .application.properties + * ---- + * %dev.quarkus.hibernate-orm.sql-load-script = import-dev.sql + * %test.quarkus.hibernate-orm.sql-load-script = import-test.sql + * %prod.quarkus.hibernate-orm.sql-load-script = no-file + * ---- + *
+ * [NOTE] + * ==== + * Quarkus supports `.sql` file with SQL statements or comments spread over multiple lines. + * Each SQL statement must be terminated by a semicolon. + * ==== + * + * @asciidoclet + */ + // @formatter:on + Optional sqlLoadScript(); +} +---- -|MAKING_LifeDifficult -|making-life-difficult +A few comments on this one: -|YeOldeJBoss -|ye-olde-jboss +* Every time you will need the indentation to be respected in the Javadoc comment (think list items spread on multiple +lines or indented source code), you will need to disable temporarily the automatic Eclipse formatter (this, even if you +don't use Eclipse as the formatter is included in our build). To do so, use the `// @formatter:off`/`// @formatter:on` +markers. Note the fact that they are separate comments and there is a space after the `//` marker. This is required. +* As you can see, you can use the full power of Asciidoctor (except for the limitation below) -|camelCaseEnum -|camel-case-enum +[WARNING] +==== +You cannot use open blocks (`--`) in your Asciidoctor documentation. +All the other types of blocks (source, admonitions...) are supported. +==== -|=== +[TIP] +==== +By default, the doc generator will use the hyphenated field name as the key of a `java.util.Map` configuration item. +To override this default and have a user-friendly key (independent of implementation details), you may use the +`io.quarkus.runtime.annotations.ConfigDocMapKey` annotation. See the following example: -To use the default behaviour which is based on implicit converter or a custom defined one add `@DefaultConverter` annotation to the configuration item -[source%nowrap,java] +[source,java] ---- +@ConfigMapping(prefix = "quarkus.some") @ConfigRoot -public class SomeLogConfig { +public interface SomeConfig { /** - * The level of logs to be written into the file. + * Namespace configuration. */ - @DefaultConverter // <1> - @ConfigItem(defaultValue = "ALL") - Level level; + @ConfigDocMapKey("cache-name") // <1> + Map namespace(); } ---- -<1> Use the default converter (built in or a custom converter) to convert `Level.class` enum. -===== +<1> This will generate a configuration map key named `quarkus.some."cache-name"` instead of `quarkus.some."namespace"`. +==== + +===== Writing Section Documentation +If you wish to generate configuration section of a given `@ConfigGroup`, Quarkus has got you covered with the +`@ConfigDocSection` annotation. See the code example below: + +[source,java] +---- +/** +* Config group related configuration. +* Amazing introduction here +*/ +@ConfigDocSection // <1> +public ConfigGroupConfig configGroup(); +---- +<1> This will add a section documentation for the `configGroup` config item in the generated documentation. +Section's title and introduction will be derived from the javadoc of the configuration item. The first sentence from the javadoc is considered as the section title and the remaining sentences used as section introduction. +You can also use the `@asciidoclet` tag as shown above. === Conditional Step Inclusion @@ -1407,7 +1363,7 @@ has two optional parameters: `onlyIf` and `onlyIfNot`. These parameters can be which implement `BooleanSupplier`. The build step will only be included when the method returns `true` (for `onlyIf`) or `false` (for `onlyIfNot`). -The condition class can inject <> as long as they belong to +The condition class can inject <> as long as they belong to a build-time phase. Run time configuration is not available for condition classes. The condition class may also inject a value of type `io.quarkus.runtime.LaunchMode`.