From 6d6115839c57dd91ceb6727d3bf637c1836a29c1 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 7 Apr 2020 13:10:34 +0100 Subject: [PATCH] Fixes #274. Added interceptor to activate a Configuration Profile. --- .../ROOT/pages/interceptors/interceptors.adoc | 29 ++++ .../ConfigSourceInterceptorFactory.java | 33 ++++ .../ProfileConfigSourceInterceptor.java | 40 +++++ .../io/smallrye/config/SmallRyeConfig.java | 35 ++-- .../config/SmallRyeConfigBuilder.java | 46 ++++- .../config/ConfigSourceInterceptorTest.java | 8 +- .../ConfigSourceProfileInterceptorTest.java | 36 ---- .../ProfileConfigSourceInterceptorTest.java | 159 ++++++++++++++++++ .../ServiceLoaderConfigSourceInterceptor.java | 5 +- ...eLoaderConfigSourceInterceptorFactory.java | 13 ++ ...lrye.config.ConfigSourceInterceptorFactory | 1 + 11 files changed, 345 insertions(+), 60 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java create mode 100644 implementation/src/main/java/io/smallrye/config/ProfileConfigSourceInterceptor.java delete mode 100644 implementation/src/test/java/io/smallrye/config/ConfigSourceProfileInterceptorTest.java create mode 100644 implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java create mode 100644 implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptorFactory.java create mode 100644 implementation/src/test/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory diff --git a/doc/modules/ROOT/pages/interceptors/interceptors.adoc b/doc/modules/ROOT/pages/interceptors/interceptors.adoc index 7592bcb46..baf5b3c93 100644 --- a/doc/modules/ROOT/pages/interceptors/interceptors.adoc +++ b/doc/modules/ROOT/pages/interceptors/interceptors.adoc @@ -41,6 +41,7 @@ interceptors are registered with the same priority, then their execution order m SmallRye Config provides the following built-in Interceptors: * <> +* <> [[expression-interceptor]] === ExpressionConfigSourceInterceptor @@ -68,3 +69,31 @@ If an expression cannot be expanded and no default is supplied a `NoSuchElementE The `ExpressionConfigSourceInterceptor` is not registered by default. You need to register it via the ServiceLoader mechanism with your application. + +[[profile-interceptor]] +=== ProfileConfigSourceInterceptor + +The `ProfileConfigSourceInterceptor` allows you to have multiple configurations with the same name and select them via +a profile property. + +To be able to set properties with the same name, you need to prefix each property with `%` followed by the profile name +and a dot: + +[source,properties] +---- +my.prop=1234 +%dev.my.prop=5678 +---- + +Lookup is always done using the `my.prop` property name. If you want to use the profile `dev`, you need to set the +configuration `smallrye.config.profile=dev` into any valid ConfigSource. + +When looking up the property `my.prop` with the `dev` profile active the value is `5678`. + +Only one profile can be active in any given time. + +==== Config Priority over Profiles + +A ConfigSource with a highest priority, that defines `my.prop` will take priority over another low priority +ConfigSource that defines `%dev.my.prop`. This allows you to override profiles properties regardless of the active +profile. diff --git a/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java new file mode 100644 index 000000000..5d45ac07f --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java @@ -0,0 +1,33 @@ +package io.smallrye.config; + +/** + * This ConfigSourceInterceptorFactory allows to initialize a {@link ConfigSourceInterceptor}, with access to the + * current {@link ConfigSourceInterceptorContext}. + * + * Interceptors in the chain are initialized in priority order and the current + * {@link ConfigSourceInterceptorContext} contains the current interceptor, plus all other interceptors already + * initialized. + */ +public abstract class ConfigSourceInterceptorFactory { + public abstract ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context); + + public abstract Class getInterceptorClass(); + + static final class Delegate extends ConfigSourceInterceptorFactory { + private final ConfigSourceInterceptor interceptor; + + Delegate(final ConfigSourceInterceptor interceptor) { + this.interceptor = interceptor; + } + + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + return interceptor; + } + + @Override + public Class getInterceptorClass() { + return interceptor.getClass(); + } + } +} diff --git a/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceInterceptor.java new file mode 100644 index 000000000..5d9ed087b --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ProfileConfigSourceInterceptor.java @@ -0,0 +1,40 @@ +package io.smallrye.config; + +import java.util.Optional; + +import javax.annotation.Priority; + +@Priority(600) +public class ProfileConfigSourceInterceptor implements ConfigSourceInterceptor { + public static final String SMALLRYE_PROFILE = "smallrye.config.profile"; + + private final String profile; + + public ProfileConfigSourceInterceptor(final String profile) { + this.profile = profile; + } + + public ProfileConfigSourceInterceptor(final ConfigSourceInterceptorContext context) { + this(context, SMALLRYE_PROFILE); + } + + public ProfileConfigSourceInterceptor( + final ConfigSourceInterceptorContext context, + final String profileConfigName) { + this.profile = Optional.ofNullable(context.proceed(profileConfigName)).map(ConfigValue::getValue).orElse(null); + } + + @Override + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + if (profile != null) { + final ConfigValue profileValue = context.proceed("%" + profile + "." + name); + if (profileValue != null) { + final ConfigValue originalValue = context.proceed(name); + return originalValue.getConfigSourceOrdinal() > profileValue.getConfigSourceOrdinal() ? originalValue + : profileValue; + } + } + + return context.proceed(name); + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index d7f84e939..1c086e76e 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -40,12 +40,12 @@ import java.util.function.IntFunction; import java.util.function.UnaryOperator; -import javax.annotation.Priority; - import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.Converter; +import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; + /** * @author Jeff Mesnil (c) 2017 Red Hat inc. */ @@ -125,19 +125,12 @@ private Map> buildConverters(final SmallRyeConfigBuilder buil } private ConfigSourceInterceptorContext buildInterceptorChain(final SmallRyeConfigBuilder builder) { - final List interceptors = new ArrayList<>(builder.getInterceptors()); + final List interceptors = new ArrayList<>(builder.getInterceptors()); if (builder.isAddDiscoveredInterceptors()) { interceptors.addAll(builder.discoverInterceptors()); } - interceptors.sort((o1, o2) -> { - final Integer p1 = Optional.ofNullable(o1.getClass().getAnnotation(Priority.class)).map(Priority::value) - .orElse(100); - final Integer p2 = Optional.ofNullable(o2.getClass().getAnnotation(Priority.class)).map(Priority::value) - .orElse(100); - - return Integer.compare(p2, p1); - }); + interceptors.sort(Comparator.comparingInt(InterceptorWithPriority::getPriority).reversed()); SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext( (ConfigSourceInterceptor) (context, name) -> { @@ -163,13 +156,29 @@ private ConfigSourceInterceptorContext buildInterceptorChain(final SmallRyeConfi return null; }, null); - for (int i = interceptors.size() - 1; i >= 0; i--) { - current = new SmallRyeConfigSourceInterceptorContext(interceptors.get(i), current); + List configSourceInterceptors = initInterceptors(interceptors, current); + for (int i = configSourceInterceptors.size() - 1; i >= 0; i--) { + current = new SmallRyeConfigSourceInterceptorContext(configSourceInterceptors.get(i), current); } return current; } + private List initInterceptors(final List interceptors, + final SmallRyeConfigSourceInterceptorContext context) { + final List configSourceInterceptors = new ArrayList<>(); + + SmallRyeConfigSourceInterceptorContext current = context; + for (InterceptorWithPriority interceptor : interceptors) { + for (ConfigSourceInterceptor sourceInterceptor : configSourceInterceptors) { + current = new SmallRyeConfigSourceInterceptorContext(sourceInterceptor, current); + } + configSourceInterceptors.add(interceptor.getInterceptor(current)); + } + + return configSourceInterceptors; + } + // no @Override public > C getValues(String name, Class itemClass, IntFunction collectionFactory) { return getValues(name, getConverter(itemClass), collectionFactory); diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 4355f7050..a9e5c4038 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -23,9 +23,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.ServiceLoader; import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Priority; @@ -46,7 +49,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private List sources = new ArrayList<>(); private Function sourceWrappers = UnaryOperator.identity(); private Map converters = new HashMap<>(); - private List interceptors = new ArrayList<>(); + private List interceptors = new ArrayList<>(); private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDiscoveredSources = false; @@ -93,11 +96,16 @@ List discoverConverters() { return discoveredConverters; } - List discoverInterceptors() { - List interceptors = new ArrayList<>(); + List discoverInterceptors() { + List interceptors = new ArrayList<>(); ServiceLoader interceptorLoader = ServiceLoader.load(ConfigSourceInterceptor.class, classLoader); - interceptorLoader.forEach(interceptors::add); + interceptorLoader.forEach(interceptor -> interceptors.add(new InterceptorWithPriority(interceptor))); + + ServiceLoader interceptorFactoryLoader = ServiceLoader + .load(ConfigSourceInterceptorFactory.class, classLoader); + interceptorFactoryLoader.forEach(interceptor -> interceptors.add(new InterceptorWithPriority(interceptor))); + return interceptors; } @@ -138,7 +146,9 @@ public SmallRyeConfigBuilder withSources(Collection configSources) } public SmallRyeConfigBuilder withInterceptors(ConfigSourceInterceptor... interceptors) { - Collections.addAll(this.interceptors, interceptors); + this.interceptors.addAll(Stream.of(interceptors) + .map(InterceptorWithPriority::new) + .collect(Collectors.toList())); return this; } @@ -203,7 +213,7 @@ Map getConverters() { return converters; } - List getInterceptors() { + List getInterceptors() { return interceptors; } @@ -245,4 +255,28 @@ int getPriority() { return priority; } } + + static class InterceptorWithPriority { + private final ConfigSourceInterceptorFactory factory; + private final int priority; + + private InterceptorWithPriority(ConfigSourceInterceptor interceptor) { + this(new ConfigSourceInterceptorFactory.Delegate(interceptor)); + } + + private InterceptorWithPriority(ConfigSourceInterceptorFactory factory) { + this.factory = factory; + this.priority = Optional.ofNullable(factory.getInterceptorClass().getAnnotation(Priority.class)) + .map(Priority::value) + .orElse(100); + } + + public ConfigSourceInterceptor getInterceptor(ConfigSourceInterceptorContext context) { + return factory.getInterceptor(context); + } + + public int getPriority() { + return priority; + } + } } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java index ab7de299b..88c9a7c92 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java @@ -37,11 +37,11 @@ public void priority() { public void serviceLoader() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "1234")) + .withSources(KeyValuesConfigSource.config("my.prop.loader", "1234")) .addDiscoveredInterceptors() .build(); - final String value = config.getValue("my.prop", String.class); + final String value = config.getValue("my.prop.loader", String.class); Assert.assertEquals("loader", value); } @@ -49,13 +49,13 @@ public void serviceLoader() { public void serviceLoaderAndPriorities() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "1234")) + .withSources(KeyValuesConfigSource.config("my.prop.loader", "1234")) .addDiscoveredInterceptors() .withInterceptors(new LowerPriorityConfigSourceInterceptor(), new HighPriorityConfigSourceInterceptor()) .build(); - final String value = config.getValue("my.prop", String.class); + final String value = config.getValue("my.prop.loader", String.class); Assert.assertEquals("higher", value); } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSourceProfileInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ConfigSourceProfileInterceptorTest.java deleted file mode 100644 index 2d243706b..000000000 --- a/implementation/src/test/java/io/smallrye/config/ConfigSourceProfileInterceptorTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.smallrye.config; - -import org.eclipse.microprofile.config.Config; -import org.junit.Assert; -import org.junit.Test; - -public class ConfigSourceProfileInterceptorTest { - @Test - public void interceptor() { - SmallRyeConfig config = (SmallRyeConfig) buildConfig("my.prop", "1234", "%prod.my.prop", "prod"); - - final String value = config.getValue("my.prop", String.class); - Assert.assertEquals("prod", value); - } - - private static Config buildConfig(String... keyValues) { - return new SmallRyeConfigBuilder() - .addDefaultSources() - .withSources(KeyValuesConfigSource.config(keyValues)) - .withInterceptors(new ProfileConfigSourceInterceptor()) - .build(); - } - - private static class ProfileConfigSourceInterceptor implements ConfigSourceInterceptor { - private final String profile = "prod"; - - @Override - public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - ConfigValue configValue = context.proceed("%" + profile + "." + name); - if (configValue == null) { - configValue = context.proceed(name); - } - return configValue; - } - } -} diff --git a/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java new file mode 100644 index 000000000..bd4bd9889 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java @@ -0,0 +1,159 @@ +package io.smallrye.config; + +import static io.smallrye.config.ProfileConfigSourceInterceptor.SMALLRYE_PROFILE; +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; + +import org.eclipse.microprofile.config.Config; +import org.junit.Test; + +import io.smallrye.config.common.MapBackedConfigSource; + +public class ProfileConfigSourceInterceptorTest { + @Test + public void profile() { + final Config config = buildConfig("my.prop", "1", "%prof.my.prop", "2", SMALLRYE_PROFILE, "prof"); + + assertEquals("2", config.getValue("my.prop", String.class)); + } + + @Test + public void fallback() { + final Config config = buildConfig("my.prop", "1", SMALLRYE_PROFILE, "prof"); + + assertEquals("1", config.getValue("my.prop", String.class)); + } + + @Test + public void expressions() { + final Config config = buildConfig("my.prop", "1", "%prof.my.prop", "${my.prop}", SMALLRYE_PROFILE, "prof"); + + assertEquals("1", config.getValue("my.prop", String.class)); + } + + @Test + public void profileExpressions() { + final Config config = buildConfig("my.prop", "1", + "%prof.my.prop", "${%prof.my.prop.profile}", + "%prof.my.prop.profile", "2", + SMALLRYE_PROFILE, "prof"); + + assertEquals("2", config.getValue("my.prop", String.class)); + } + + @Test + public void customConfigProfile() { + final String[] configs = { "my.prop", "1", "%prof.my.prop", "2", "config.profile", "prof" }; + final Config config = new SmallRyeConfigBuilder() + .addDefaultSources() + .addDiscoveredInterceptors() + .withSources(KeyValuesConfigSource.config(configs)) + .build(); + + assertEquals("2", config.getValue("my.prop", String.class)); + } + + @Test + public void noConfigProfile() { + final String[] configs = { "my.prop", "1", "%prof.my.prop", "2" }; + final Config config = new SmallRyeConfigBuilder() + .addDefaultSources() + .withSources(KeyValuesConfigSource.config(configs)) + .withInterceptors( + new ProfileConfigSourceInterceptor("prof"), + new ExpressionConfigSourceInterceptor()) + .build(); + + assertEquals("2", config.getValue("my.prop", String.class)); + } + + @Test + public void priorityProfile() { + final Config config = new SmallRyeConfigBuilder() + .addDefaultSources() + .withSources( + new MapBackedConfigSource("higher", new HashMap() { + { + put("%prof.my.prop", "higher-profile"); + } + }, 200) { + }, + new MapBackedConfigSource("lower", new HashMap() { + { + put("my.prop", "lower"); + put("%prof.my.prop", "lower-profile"); + } + }, 100) { + }) + .withInterceptors( + new ProfileConfigSourceInterceptor("prof"), + new ExpressionConfigSourceInterceptor()) + .build(); + + assertEquals("higher-profile", config.getValue("my.prop", String.class)); + } + + @Test + public void priorityOverrideProfile() { + final Config config = new SmallRyeConfigBuilder() + .addDefaultSources() + .withSources( + new MapBackedConfigSource("higher", new HashMap() { + { + put("my.prop", "higher"); + } + }, 200) { + }, + new MapBackedConfigSource("lower", new HashMap() { + { + put("my.prop", "lower"); + put("%prof.my.prop", "lower-profile"); + } + }, 100) { + }) + .withInterceptors( + new ProfileConfigSourceInterceptor("prof"), + new ExpressionConfigSourceInterceptor()) + .build(); + + assertEquals("higher", config.getValue("my.prop", String.class)); + } + + @Test + public void priorityProfileOverOriginal() { + final Config config = new SmallRyeConfigBuilder() + .addDefaultSources() + .withSources( + new MapBackedConfigSource("higher", new HashMap() { + { + put("my.prop", "higher"); + put("%prof.my.prop", "higher-profile"); + } + }, 200) { + }, + new MapBackedConfigSource("lower", new HashMap() { + { + put("my.prop", "lower"); + put("%prof.my.prop", "lower-profile"); + } + }, 100) { + }) + .withInterceptors( + new ProfileConfigSourceInterceptor("prof"), + new ExpressionConfigSourceInterceptor()) + .build(); + + assertEquals("higher-profile", config.getValue("my.prop", String.class)); + } + + private static Config buildConfig(String... keyValues) { + return new SmallRyeConfigBuilder() + .addDefaultSources() + .withSources(KeyValuesConfigSource.config(keyValues)) + .withInterceptors( + new ProfileConfigSourceInterceptor("prof"), + new ExpressionConfigSourceInterceptor()) + .build(); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptor.java b/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptor.java index 7d5856c5c..8d9170380 100644 --- a/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptor.java +++ b/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptor.java @@ -3,6 +3,9 @@ public class ServiceLoaderConfigSourceInterceptor implements ConfigSourceInterceptor { @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - return ConfigValue.builder().withName(name).withValue("loader").build(); + if ("my.prop.loader".equals(name)) { + return ConfigValue.builder().withName(name).withValue("loader").build(); + } + return context.proceed(name); } } diff --git a/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptorFactory.java b/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptorFactory.java new file mode 100644 index 000000000..0c6fae8dc --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/ServiceLoaderConfigSourceInterceptorFactory.java @@ -0,0 +1,13 @@ +package io.smallrye.config; + +public class ServiceLoaderConfigSourceInterceptorFactory extends ConfigSourceInterceptorFactory { + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + return new ProfileConfigSourceInterceptor(context, "config.profile"); + } + + @Override + public Class getInterceptorClass() { + return ProfileConfigSourceInterceptor.class; + } +} diff --git a/implementation/src/test/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory b/implementation/src/test/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory new file mode 100644 index 000000000..fe460f58b --- /dev/null +++ b/implementation/src/test/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory @@ -0,0 +1 @@ +io.smallrye.config.ServiceLoaderConfigSourceInterceptorFactory