From c519eb7ad491deba7ff16b91b7e5be00c97cc61b Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 30 Aug 2023 09:15:06 +0100 Subject: [PATCH] Generated dotted properties from Env before other interceptors, so the properties can be modified by profiles, relocates, etc. (#987) --- .../PropertyNamesConfigSourceInterceptor.java | 68 +++++++++++++++++++ .../io/smallrye/config/SmallRyeConfig.java | 68 ++----------------- .../config/ConfigMappingInterfaceTest.java | 22 ++++++ 3 files changed, 94 insertions(+), 64 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/PropertyNamesConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/PropertyNamesConfigSourceInterceptor.java index 3e58d24c2..7eab09fc5 100644 --- a/implementation/src/main/java/io/smallrye/config/PropertyNamesConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/PropertyNamesConfigSourceInterceptor.java @@ -1,9 +1,15 @@ package io.smallrye.config; +import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; +import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted; + import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; +import org.eclipse.microprofile.config.spi.ConfigSource; + /** * This interceptor adds additional entries to {@link org.eclipse.microprofile.config.Config#getPropertyNames}. */ @@ -12,6 +18,11 @@ class PropertyNamesConfigSourceInterceptor implements ConfigSourceInterceptor { private final Set properties = new HashSet<>(); + public PropertyNamesConfigSourceInterceptor(final ConfigSourceInterceptorContext context, + final List sources) { + this.properties.addAll(generateDottedProperties(context, sources)); + } + @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { return context.proceed(name); @@ -31,4 +42,61 @@ public Iterator iterateNames(final ConfigSourceInterceptorContext contex void addProperties(final Set properties) { this.properties.addAll(properties); } + + /** + * Generate dotted properties from Env properties. + *
+ * These are required when a consumer relies on the list of properties to find additional + * configurations. The list of properties is not normalized due to environment variables, which follow specific + * naming rules. The MicroProfile Config specification defines a set of conversion rules to look up and find + * values from environment variables even when using their dotted version, but this does not apply to the + * properties list. + *
+ * Because an environment variable name may only be represented by a subset of characters, it is not possible + * to represent exactly a dotted version name from an environment variable name. Additional dotted properties + * mapped from environment variables are only added if a relationship cannot be found between all properties + * using the conversions look up rules of the MicroProfile Config specification. Example: + *
+ * If foo.bar is present and FOO_BAR is also present, no property is required. + * If foo-bar is present and FOO_BAR is also present, no property is required. + * If FOO_BAR is present a property foo.bar is required. + */ + private static Set generateDottedProperties(final ConfigSourceInterceptorContext current, + final List sources) { + // Collect all known properties + Set properties = new HashSet<>(); + Iterator iterateNames = current.iterateNames(); + while (iterateNames.hasNext()) { + properties.add(iterateNames.next()); + } + + // Collect only properties from the EnvSources + Set envProperties = new HashSet<>(); + for (ConfigSource source : sources) { + if (source instanceof EnvConfigSource) { + envProperties.addAll(source.getPropertyNames()); + } + } + properties.removeAll(envProperties); + + // Collect properties that have the same semantic meaning + Set overrides = new HashSet<>(); + for (String property : properties) { + String semanticProperty = replaceNonAlphanumericByUnderscores(property); + for (String envProperty : envProperties) { + if (envProperty.equalsIgnoreCase(semanticProperty)) { + overrides.add(envProperty); + break; + } + } + } + + // Remove them - Remaining properties can only be found in the EnvSource - generate a dotted version + envProperties.removeAll(overrides); + Set dottedProperties = new HashSet<>(); + for (String envProperty : envProperties) { + dottedProperties.add(toLowerCaseAndDotted(envProperty)); + } + return dottedProperties; + } } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index c4c3c98ed..d207e3577 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -16,8 +16,6 @@ package io.smallrye.config; import static io.smallrye.config.ConfigSourceInterceptor.EMPTY; -import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; -import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted; import java.io.ObjectStreamException; import java.io.Serializable; @@ -553,22 +551,21 @@ private static class ConfigSources implements Serializable { List profiles = getProfiles(interceptors); List sourcesWithPriorities = mapLateSources(sources, interceptors, current, profiles, builder); + List configSources = getSources(sourcesWithPriorities); // Rebuild the chain with the late sources and new instances of the interceptors // The new instance will ensure that we get rid of references to factories and other stuff and keep only // the resolved final source or interceptor to use. current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities), current); + PropertyNamesConfigSourceInterceptor propertyNamesInterceptor = new PropertyNamesConfigSourceInterceptor(current, + configSources); + current = new SmallRyeConfigSourceInterceptorContext(propertyNamesInterceptor, current); for (ConfigSourceInterceptor interceptor : interceptors) { current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); } - // PropertyNames and generate additional properties - List configSources = getSources(sourcesWithPriorities); - PropertyNamesConfigSourceInterceptor propertyNamesInterceptor = new PropertyNamesConfigSourceInterceptor(); - current = new SmallRyeConfigSourceInterceptorContext(propertyNamesInterceptor, current); PropertyNames propertyNames = new PropertyNames(propertyNamesInterceptor); - propertyNames.add(generateDottedProperties(configSources, current)); this.profiles = profiles; this.sources = configSources; @@ -714,63 +711,6 @@ private static List getConfigurableSources(final List< return Collections.unmodifiableList(configurableConfigSources); } - /** - * Generate dotted properties from Env properties. - *
- * These are required when a consumer relies on the list of properties to find additional - * configurations. The list of properties is not normalized due to environment variables, which follow specific - * naming rules. The MicroProfile Config specification defines a set of conversion rules to look up and find - * values from environment variables even when using their dotted version, but this does not apply to the - * properties list. - *
- * Because an environment variable name may only be represented by a subset of characters, it is not possible - * to represent exactly a dotted version name from an environment variable name. Additional dotted properties - * mapped from environment variables are only added if a relationship cannot be found between all properties - * using the conversions look up rules of the MicroProfile Config specification. Example: - *
- * If foo.bar is present and FOO_BAR is also present, no property is required. - * If foo-bar is present and FOO_BAR is also present, no property is required. - * If FOO_BAR is present a property foo.bar is required. - */ - private static Set generateDottedProperties(final List sources, - final SmallRyeConfigSourceInterceptorContext current) { - // Collect all known properties - Set properties = new HashSet<>(); - Iterator iterateNames = current.iterateNames(); - while (iterateNames.hasNext()) { - properties.add(iterateNames.next()); - } - - // Collect only properties from the EnvSources - Set envProperties = new HashSet<>(); - for (ConfigSource source : sources) { - if (source instanceof EnvConfigSource) { - envProperties.addAll(source.getPropertyNames()); - } - } - properties.removeAll(envProperties); - - // Collect properties that have the same semantic meaning - Set overrides = new HashSet<>(); - for (String property : properties) { - String semanticProperty = replaceNonAlphanumericByUnderscores(property); - for (String envProperty : envProperties) { - if (envProperty.equalsIgnoreCase(semanticProperty)) { - overrides.add(envProperty); - break; - } - } - } - - // Remove them - Remaining properties can only be found in the EnvSource - generate a dotted version - envProperties.removeAll(overrides); - Set dottedProperties = new HashSet<>(); - for (String envProperty : envProperties) { - dottedProperties.add(toLowerCaseAndDotted(envProperty)); - } - return dottedProperties; - } - List getProfiles() { return profiles; } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java index 0e05a97aa..95d7d0275 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java @@ -2307,4 +2307,26 @@ void mapKeyQuotes() { interface MapKeyQuotes { Map values(); } + + @Test + void mapWithEnvVarsOnlyInProfile() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("map.env.one", "one", "%dev.map.env.two", "two")) + .withSources(new EnvConfigSource(Map.of("_DEV_MAP_ENV_THREE", "3"), 100)) + .withMapping(MapWithEnvVarsOnlyInProfile.class) + .withProfile("dev") + .build(); + + MapWithEnvVarsOnlyInProfile mapping = config.getConfigMapping(MapWithEnvVarsOnlyInProfile.class); + + assertEquals("one", mapping.map().get("one")); + assertEquals("two", mapping.map().get("two")); + assertEquals("3", mapping.map().get("three")); + } + + @ConfigMapping(prefix = "map.env") + interface MapWithEnvVarsOnlyInProfile { + @WithParentName + Map map(); + } }