From 8a472160bfb083d29a346efe97f66ad1957ef295 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 31 Jul 2024 16:22:39 +0100 Subject: [PATCH] Keep a reference to the full interceptor chain to call restart --- .../io/smallrye/config/SmallRyeConfig.java | 38 ++--- ...mallRyeConfigSourceInterceptorContext.java | 26 ++- .../config/ConfigSourceInterceptorTest.java | 150 ++++++++++++++++-- 3 files changed, 174 insertions(+), 40 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 4cf379f00..b1eada4d3 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -57,6 +57,7 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; +import io.smallrye.config.SmallRyeConfigSourceInterceptorContext.InterceptorChain; import io.smallrye.config._private.ConfigLogging; import io.smallrye.config._private.ConfigMessages; import io.smallrye.config.common.utils.StringUtil; @@ -747,10 +748,6 @@ public List getProfiles() { return configSources.getProfiles(); } - ConfigSourceInterceptorContext interceptorChain() { - return configSources.interceptorChain; - } - private static class ConfigSources implements Serializable { private static final long serialVersionUID = 3483018375584151712L; @@ -780,21 +777,23 @@ private static class ConfigSources implements Serializable { List interceptorWithPriorities = buildInterceptors(builder); // Create the initial chain with initial sources and all interceptors - SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, config); - current = new SmallRyeConfigSourceInterceptorContext(negativeSources, current, config); + InterceptorChain chain = new InterceptorChain(); + SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, chain); + + current = new SmallRyeConfigSourceInterceptorContext(negativeSources, current, chain); for (InterceptorWithPriority interceptorWithPriority : interceptorWithPriorities) { if (interceptorWithPriority.getPriority() < 0) { ConfigSourceInterceptor interceptor = interceptorWithPriority.getInterceptor(current); negativeInterceptors.add(interceptor); - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, chain); } } - current = new SmallRyeConfigSourceInterceptorContext(positiveSources, current, config); + current = new SmallRyeConfigSourceInterceptorContext(positiveSources, current, chain); for (InterceptorWithPriority interceptorWithPriority : interceptorWithPriorities) { if (interceptorWithPriority.getPriority() >= 0) { ConfigSourceInterceptor interceptor = interceptorWithPriority.getInterceptor(current); positiveInterceptors.add(interceptor); - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, chain); } } @@ -807,16 +806,16 @@ private static class ConfigSources implements Serializable { // 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, config); + current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, chain); current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities, true), - current, config); + current, chain); for (ConfigSourceInterceptor interceptor : negativeInterceptors) { - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, chain); } current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities, false), - current, config); + current, chain); for (ConfigSourceInterceptor interceptor : positiveInterceptors) { - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, chain); } this.profiles = profiles; @@ -913,16 +912,17 @@ private static List mapLateSources( Collections.reverse(currentSources); // Rebuild the chain with the profiles sources, so profiles values are also available in factories - ConfigSourceInterceptorContext context = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, config); + InterceptorChain chain = new InterceptorChain(); + ConfigSourceInterceptorContext context = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, chain); context = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(currentSources, true), context, - config); + chain); for (ConfigSourceInterceptor interceptor : negativeInterceptors) { - context = new SmallRyeConfigSourceInterceptorContext(interceptor, context, config); + context = new SmallRyeConfigSourceInterceptorContext(interceptor, context, chain); } context = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(currentSources, false), context, - config); + chain); for (ConfigSourceInterceptor interceptor : positiveInterceptors) { - context = new SmallRyeConfigSourceInterceptorContext(interceptor, context, config); + context = new SmallRyeConfigSourceInterceptorContext(interceptor, context, chain); } // Init remaining sources, coming from SmallRyeConfig diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java index 6643fa804..bcd5c567d 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java @@ -1,23 +1,25 @@ package io.smallrye.config; +import java.io.Serializable; import java.util.Iterator; +import java.util.function.Supplier; class SmallRyeConfigSourceInterceptorContext implements ConfigSourceInterceptorContext { private static final long serialVersionUID = 6654406739008729337L; private final ConfigSourceInterceptor interceptor; private final ConfigSourceInterceptorContext next; - private final SmallRyeConfig config; + private final InterceptorChain chain; private static final ThreadLocal rcHolder = ThreadLocal.withInitial(RecursionCount::new); SmallRyeConfigSourceInterceptorContext( final ConfigSourceInterceptor interceptor, final ConfigSourceInterceptorContext next, - final SmallRyeConfig config) { + final InterceptorChain chain) { this.interceptor = interceptor; this.next = next; - this.config = config; + this.chain = chain.setChain(this); } @Override @@ -30,7 +32,7 @@ public ConfigValue restart(final String name) { RecursionCount rc = rcHolder.get(); rc.increment(); try { - return config.interceptorChain().proceed(name); + return chain.get().proceed(name); } finally { if (rc.decrement()) { // avoid leaking if the thread is cached @@ -44,6 +46,22 @@ public Iterator iterateNames() { return interceptor.iterateNames(next); } + static class InterceptorChain implements Supplier, Serializable { + private static final long serialVersionUID = 7387475787257736307L; + + private ConfigSourceInterceptorContext chain; + + @Override + public ConfigSourceInterceptorContext get() { + return chain; + } + + public InterceptorChain setChain(final ConfigSourceInterceptorContext chain) { + this.chain = chain; + return this; + } + } + static final class RecursionCount { int count; diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java index 88e79f201..36616e45c 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigSourceInterceptorTest.java @@ -1,18 +1,26 @@ package io.smallrye.config; +import static io.smallrye.config.KeyValuesConfigSource.config; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jakarta.annotation.Priority; +import org.eclipse.microprofile.config.spi.ConfigSource; import org.jboss.logging.Logger; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -24,12 +32,12 @@ class ConfigSourceInterceptorTest { void interceptor() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "1234", "override", "4567")) + .withSources(config("my.prop", "1234", "override", "4567")) .withInterceptors(new LoggingConfigSourceInterceptor(), new OverridingConfigSourceInterceptor()) .build(); - final String value = config.getValue("my.prop", String.class); + String value = config.getValue("my.prop", String.class); Assertions.assertEquals("4567", value); } @@ -37,12 +45,12 @@ void interceptor() { void priority() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "1234")) + .withSources(config("my.prop", "1234")) .withInterceptors(new LowerPriorityConfigSourceInterceptor(), new HighPriorityConfigSourceInterceptor()) .build(); - final String value = config.getValue("my.prop", String.class); + String value = config.getValue("my.prop", String.class); Assertions.assertEquals("higher", value); } @@ -50,11 +58,11 @@ void priority() { void serviceLoader() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop.loader", "1234")) + .withSources(config("my.prop.loader", "1234")) .addDiscoveredInterceptors() .build(); - final String value = config.getValue("my.prop.loader", String.class); + String value = config.getValue("my.prop.loader", String.class); Assertions.assertEquals("loader", value); } @@ -62,13 +70,13 @@ void serviceLoader() { void serviceLoaderAndPriorities() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop.loader", "1234")) + .withSources(config("my.prop.loader", "1234")) .addDiscoveredInterceptors() .withInterceptors(new LowerPriorityConfigSourceInterceptor(), new HighPriorityConfigSourceInterceptor()) .build(); - final String value = config.getValue("my.prop.loader", String.class); + String value = config.getValue("my.prop.loader", String.class); Assertions.assertEquals("higher", value); } @@ -76,7 +84,7 @@ void serviceLoaderAndPriorities() { void defaultInterceptors() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "1", + .withSources(config("my.prop", "1", "%prof.my.prop", "${%prof.my.prop.profile}", "%prof.my.prop.profile", "2", SMALLRYE_CONFIG_PROFILE, "prof")) @@ -90,7 +98,7 @@ void defaultInterceptors() { void notFailExpansionInactive() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("my.prop", "${expansion}", + .withSources(config("my.prop", "${expansion}", "%prof.my.prop", "${%prof.my.prop.profile}", "%prof.my.prop.profile", "2", SMALLRYE_CONFIG_PROFILE, "prof")) @@ -105,7 +113,7 @@ void names() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .addDefaultInterceptors() - .withSources(KeyValuesConfigSource.config("my.prop", "1", + .withSources(config("my.prop", "1", "%prof.my.prop", "${%prof.my.prop.profile}", "%prof.my.prop.profile", "2", SMALLRYE_CONFIG_PROFILE, "prof")) @@ -122,7 +130,7 @@ void names() { void replaceNames() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() - .withSources(KeyValuesConfigSource.config("my.prop", "1")) + .withSources(config("my.prop", "1")) .withInterceptors(new ConfigSourceInterceptor() { @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { @@ -148,7 +156,7 @@ void expandActiveProfile() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .addDefaultInterceptors() - .withSources(KeyValuesConfigSource.config( + .withSources(config( "app.http.port", "8081", "%dev.app.http.port", "8082", "%test.app.http.port", "8083", @@ -165,15 +173,123 @@ void priorityInParentClass() { assertEquals(1, interceptorWithPriority.getPriority()); } + @Test + void restart() { + List counter = new ArrayList<>(); + counter.add(0); + + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .withSources(config("final", "final")) + .withInterceptorFactories(new ConfigSourceInterceptorFactory() { + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + return new ConfigSourceInterceptor() { + @Override + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + counter.set(0, counter.get(0) + 1); + return context.proceed(name); + } + }; + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(DEFAULT_PRIORITY + 100); + } + }) + .withInterceptors(new ConfigSourceInterceptor() { + @Override + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + if (name.equals("restart")) { + return context.restart("final"); + } + return context.proceed(name); + } + }) + .build(); + + assertEquals("final", config.getRawValue("restart")); + assertEquals(2, counter.get(0)); + assertEquals("final", config.getConfigValue("final").getName()); + assertEquals("final", config.getConfigValue("restart").getName()); + } + + @Test + void restartNotInitialized() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .withSources(config("final", "final")) + .withInterceptors(new ConfigSourceInterceptor() { + @Override + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + if (name.equals("restart")) { + return context.restart("final"); + } + return context.proceed(name); + } + }) + .withSources(new ConfigSourceFactory() { + @Override + public Iterable getConfigSources(final ConfigSourceContext context) { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(new ConfigSourceContext.ConfigSourceContextConfigSource(context)) + .withMapping(Restart.class) + .withMappingIgnore("*") + .build(); + return emptyList(); + } + }) + .build(); + + assertEquals("final", config.getRawValue("restart")); + } + + @ConfigMapping + interface Restart { + Optional restart(); + } + + @Test + void supplier() { + Value value = new Value(); + + Supplier first = new Supplier<>() { + @Override + public Value get() { + return value; + } + }; + + Supplier second = new Supplier<>() { + @Override + public Value get() { + return value; + } + }; + + System.out.println(first.get().value); + System.out.println(second.get().value); + + value.value = "something else"; + + System.out.println(first.get().value); + System.out.println(second.get().value); + } + + public static class Value { + String value = "value"; + } + private static class LoggingConfigSourceInterceptor implements ConfigSourceInterceptor { private static final Logger LOG = Logger.getLogger("io.smallrye.config"); @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - final ConfigValue configValue = context.proceed(name); - final String key = configValue.getName(); - final String value = configValue.getValue(); - final String configSource = configValue.getConfigSourceName(); + ConfigValue configValue = context.proceed(name); + String key = configValue.getName(); + String value = configValue.getValue(); + String configSource = configValue.getConfigSourceName(); LOG.infov("The key {0} was loaded from {1} with the value {2}", key, configSource, value);