From bf945f55dbf2f06f002d39d4524750749c9d07cf Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 30 Jun 2022 15:03:16 +0100 Subject: [PATCH] Load Profile Sources before Factories so the profile values are available in the Factory context (#771) --- .../AbstractLocationConfigSourceLoader.java | 37 +++---- .../config/ConfigurableConfigSource.java | 5 +- .../config/ProfileConfigSourceFactory.java | 20 ++++ .../io/smallrye/config/SmallRyeConfig.java | 100 ++++++++++-------- .../config/SmallRyeConfigSourceContext.java | 30 ++++++ ...ertiesLocationConfigSourceFactoryTest.java | 68 ++++++++++++ 6 files changed, 193 insertions(+), 67 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/ProfileConfigSourceFactory.java create mode 100644 implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceContext.java diff --git a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java index ca8af88b9..fb6ae2687 100644 --- a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java +++ b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.OptionalInt; import java.util.function.Consumer; import org.eclipse.microprofile.config.spi.ConfigSource; @@ -188,14 +189,22 @@ protected List tryHttpResource(final URI uri, final int ordinal) { protected List tryProfiles(final URI uri, final ConfigSource mainSource) { final List configSources = new ArrayList<>(); - configSources.add(new ConfigurableConfigSource((ProfileConfigSourceFactory) profiles -> { - final List profileSources = new ArrayList<>(); - for (int i = profiles.size() - 1; i >= 0; i--) { - final int ordinal = mainSource.getOrdinal() + profiles.size() - i; - final URI profileUri = addProfileName(uri, profiles.get(i)); - addProfileConfigSource(toURL(profileUri), ordinal, profileSources); + configSources.add(new ConfigurableConfigSource(new ProfileConfigSourceFactory() { + @Override + public Iterable getProfileConfigSources(final List profiles) { + final List profileSources = new ArrayList<>(); + for (int i = profiles.size() - 1; i >= 0; i--) { + final int ordinal = mainSource.getOrdinal() + profiles.size() - i; + final URI profileUri = addProfileName(uri, profiles.get(i)); + AbstractLocationConfigSourceLoader.this.addProfileConfigSource(toURL(profileUri), ordinal, profileSources); + } + return profileSources; + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(mainSource.getOrdinal()); } - return profileSources; })); return configSources; } @@ -316,18 +325,4 @@ private static URI decodeIfNeeded(final URI uri) { return uri; } } - - interface ProfileConfigSourceFactory extends ConfigSourceFactory { - @Override - default Iterable getConfigSources(final ConfigSourceContext context) { - final List profiles = context.getProfiles(); - if (profiles.isEmpty()) { - return Collections.emptyList(); - } - - return getProfileConfigSources(profiles); - } - - Iterable getProfileConfigSources(final List profiles); - } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigurableConfigSource.java b/implementation/src/main/java/io/smallrye/config/ConfigurableConfigSource.java index 5b38768ad..10b17070c 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigurableConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigurableConfigSource.java @@ -46,10 +46,7 @@ ConfigSourceFactory getFactory() { } List getConfigSources(final ConfigSourceContext context) { - return unwrap(context, new ArrayList<>()); - } - - private List unwrap(final ConfigSourceContext context, final List configSources) { + final List configSources = new ArrayList<>(); for (final ConfigSource configSource : factory.getConfigSources(context)) { if (configSource instanceof ConfigurableConfigSource) { configSources.addAll(((ConfigurableConfigSource) configSource).getConfigSources(context)); diff --git a/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceFactory.java b/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceFactory.java new file mode 100644 index 000000000..b10937d93 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceFactory.java @@ -0,0 +1,20 @@ +package io.smallrye.config; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +interface ProfileConfigSourceFactory extends ConfigSourceFactory { + @Override + default Iterable getConfigSources(final ConfigSourceContext context) { + final List profiles = context.getProfiles(); + if (profiles.isEmpty()) { + return Collections.emptyList(); + } + + return getProfileConfigSources(profiles); + } + + Iterable getProfileConfigSources(final List profiles); +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 17cc2b748..82ec9a0ef 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -538,15 +538,14 @@ private static class ConfigSources implements Serializable { // Init all late sources List profiles = getProfiles(interceptors); - List sourcesWithPriorities = mapLateSources(current, sources, profiles, - builder.isAddDiscoveredSources()); + List sourcesWithPriorities = mapLateSources(sources, interceptors, current, profiles, + builder); // 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); + current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities), current); for (ConfigSourceInterceptor interceptor : interceptors) { current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); } @@ -612,73 +611,90 @@ private static List getProfiles(final List inte } private static List mapLateSources( - final SmallRyeConfigSourceInterceptorContext initChain, final List sources, + final List interceptors, + final ConfigSourceInterceptorContext current, final List profiles, - final boolean addDiscoveredSources) { + final SmallRyeConfigBuilder builder) { ConfigSourceWithPriority.resetLoadPriority(); - - List lateSources = new ArrayList<>(); - for (ConfigSource source : sources) { - if (source instanceof ConfigurableConfigSource) { - lateSources.add((ConfigurableConfigSource) source); + List currentSources = new ArrayList<>(); + + // Init all profile sources first + List profileSources = new ArrayList<>(); + ConfigSourceContext mainContext = new SmallRyeConfigSourceContext(current, profiles); + for (ConfigurableConfigSource profileSource : getConfigurableSources(sources)) { + if (profileSource.getFactory() instanceof ProfileConfigSourceFactory) { + profileSources.addAll(profileSource.getConfigSources(mainContext)); } } - lateSources.sort(Comparator.comparingInt(ConfigurableConfigSource::getOrdinal)); - int countSourcesFromLocations = 0; - List sourcesWithPriority = new ArrayList<>(); - for (ConfigurableConfigSource configurableSource : lateSources) { - final List configSources = configurableSource.getConfigSources(new ConfigSourceContext() { - @Override - public ConfigValue getValue(final String name) { - ConfigValue value = initChain.proceed(name); - return value != null ? value : ConfigValue.builder().withName(name).build(); - } + // Sort the profiles sources with the main sources + currentSources.addAll(mapSources(profileSources)); + currentSources.addAll(mapSources(sources)); + currentSources.sort(null); + Collections.reverse(currentSources); - @Override - public List getProfiles() { - return profiles; - } + // Rebuild the chain with the profiles sources, so profiles values are also available in factories + ConfigSourceInterceptorContext context = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); + context = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(currentSources), context); + for (ConfigSourceInterceptor interceptor : interceptors) { + context = new SmallRyeConfigSourceInterceptorContext(interceptor, context); + } - @Override - public Iterator iterateNames() { - return initChain.iterateNames(); + // Init remaining sources, coming from SmallRyeConfig + int countSourcesFromLocations = 0; + List lateSources = new ArrayList<>(); + ConfigSourceContext profileContext = new SmallRyeConfigSourceContext(context, profiles); + for (ConfigurableConfigSource lateSource : getConfigurableSources(sources)) { + if (!(lateSource.getFactory() instanceof ProfileConfigSourceFactory)) { + List configSources = lateSource.getConfigSources(profileContext); + + if (lateSource.getFactory() instanceof AbstractLocationConfigSourceFactory) { + countSourcesFromLocations = countSourcesFromLocations + configSources.size(); } - }); - - if (configurableSource.getFactory() instanceof AbstractLocationConfigSourceFactory) { - countSourcesFromLocations = countSourcesFromLocations + configSources.size(); - } - for (ConfigSource configSource : configSources) { - sourcesWithPriority.add(new ConfigSourceWithPriority(configSource)); + lateSources.addAll(configSources); } } - if (countSourcesFromLocations == 0 && addDiscoveredSources) { - ConfigValue locations = initChain.proceed(SMALLRYE_CONFIG_LOCATIONS); + if (countSourcesFromLocations == 0 && builder.isAddDiscoveredSources()) { + ConfigValue locations = profileContext.getValue(SMALLRYE_CONFIG_LOCATIONS); if (locations != null && locations.getValue() != null) { ConfigLogging.log.configLocationsNotFound(SMALLRYE_CONFIG_LOCATIONS, locations.getValue()); } } - sourcesWithPriority.addAll(mapSources(sources)); - sourcesWithPriority.sort(null); - Collections.reverse(sourcesWithPriority); + // Sort the final sources + currentSources.clear(); + currentSources.addAll(mapSources(lateSources)); + currentSources.addAll(mapSources(profileSources)); + currentSources.addAll(mapSources(sources)); + currentSources.sort(null); + Collections.reverse(currentSources); - return sourcesWithPriority; + return currentSources; } private static List getSources(final List sourceWithPriorities) { - final List configSources = new ArrayList<>(); + List configSources = new ArrayList<>(); for (ConfigSourceWithPriority configSourceWithPriority : sourceWithPriorities) { configSources.add(configSourceWithPriority.getSource()); } return Collections.unmodifiableList(configSources); } + private static List getConfigurableSources(final List sources) { + List configurableConfigSources = new ArrayList<>(); + for (ConfigSource source : sources) { + if (source instanceof ConfigurableConfigSource) { + configurableConfigSources.add((ConfigurableConfigSource) source); + } + } + configurableConfigSources.sort(Comparator.comparingInt(ConfigurableConfigSource::getOrdinal).reversed()); + return Collections.unmodifiableList(configurableConfigSources); + } + /** * Generate dotted properties from Env properties. * diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceContext.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceContext.java new file mode 100644 index 000000000..8c781ff42 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceContext.java @@ -0,0 +1,30 @@ +package io.smallrye.config; + +import java.util.Iterator; +import java.util.List; + +class SmallRyeConfigSourceContext implements ConfigSourceContext { + private final ConfigSourceInterceptorContext context; + private final List profiles; + + public SmallRyeConfigSourceContext(final ConfigSourceInterceptorContext context, final List profiles) { + this.context = context; + this.profiles = profiles; + } + + @Override + public ConfigValue getValue(final String name) { + ConfigValue value = context.proceed(name); + return value != null ? value : ConfigValue.builder().withName(name).build(); + } + + @Override + public List getProfiles() { + return profiles; + } + + @Override + public Iterator iterateNames() { + return context.iterateNames(); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/PropertiesLocationConfigSourceFactoryTest.java b/implementation/src/test/java/io/smallrye/config/PropertiesLocationConfigSourceFactoryTest.java index 14741f1d6..6b395117a 100644 --- a/implementation/src/test/java/io/smallrye/config/PropertiesLocationConfigSourceFactoryTest.java +++ b/implementation/src/test/java/io/smallrye/config/PropertiesLocationConfigSourceFactoryTest.java @@ -9,14 +9,20 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.FileOutputStream; +import java.io.IOException; import java.io.StringWriter; import java.net.InetSocketAddress; +import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import java.util.logging.Level; import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -24,6 +30,7 @@ import com.sun.net.httpserver.HttpServer; +import io.smallrye.config.common.MapBackedConfigSource; import io.smallrye.testing.logging.LogCapture; class PropertiesLocationConfigSourceFactoryTest { @@ -281,6 +288,67 @@ void warningConfigLocationsNotFoundFromExisting() { assertTrue(logCapture.records().isEmpty()); } + @Test + void profileSourcesInContext(@TempDir Path tempDir) throws Exception { + Properties mainProperties = new Properties(); + mainProperties.setProperty("config_ordinal", "150"); + mainProperties.setProperty("my.prop.main", "main"); + try (FileOutputStream out = new FileOutputStream(tempDir.resolve("config.properties").toFile())) { + mainProperties.store(out, null); + } + + Properties devProperties = new Properties(); + devProperties.setProperty("my.prop.dev", "dev"); + try (FileOutputStream out = new FileOutputStream(tempDir.resolve("config-dev.properties").toFile())) { + devProperties.store(out, null); + } + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultSources() + .addDiscoveredSources() + .addDefaultInterceptors() + .withProfile("dev") + .withSources(new ConfigSourceProvider() { + @Override + public Iterable getConfigSources(final ClassLoader forClassLoader) { + AbstractLocationConfigSourceLoader configSourceLoader = new AbstractLocationConfigSourceLoader() { + @Override + protected String[] getFileExtensions() { + return new String[] { "properties" }; + } + + @Override + protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { + return new PropertiesConfigSource(url, ordinal); + } + }; + + return configSourceLoader.loadConfigSources(tempDir.resolve("config.properties").toUri().toString(), + 250); + } + }) + .withSources(new ConfigSourceFactory() { + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + Map values = new HashMap<>(); + values.put("context.main", context.getValue("my.prop.main").getRawValue()); + values.put("context.dev", context.getValue("my.prop.dev").getRawValue()); + return Collections.singletonList(new MapBackedConfigSource("map", values) { + }); + } + }) + .build(); + + assertEquals("main", config.getRawValue("my.prop.main")); + assertEquals("dev", config.getRawValue("my.prop.dev")); + assertEquals("main", config.getRawValue("context.main")); + assertEquals("dev", config.getRawValue("context.dev")); + + for (final ConfigSource configSource : config.getConfigSources()) { + System.out.println("configSource = " + configSource); + } + } + private static SmallRyeConfig buildConfig(String... locations) { return new SmallRyeConfigBuilder() .addDiscoveredSources()