diff --git a/implementation/src/main/java/io/smallrye/config/ConfigSourceContext.java b/implementation/src/main/java/io/smallrye/config/ConfigSourceContext.java new file mode 100644 index 000000000..997a4578d --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceContext.java @@ -0,0 +1,8 @@ +package io.smallrye.config; + +import io.smallrye.common.annotation.Experimental; + +@Experimental("") +public interface ConfigSourceContext { + ConfigValue getValue(String name); +} diff --git a/implementation/src/main/java/io/smallrye/config/ConfigSourceFactory.java b/implementation/src/main/java/io/smallrye/config/ConfigSourceFactory.java new file mode 100644 index 000000000..a3b84a444 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceFactory.java @@ -0,0 +1,16 @@ +package io.smallrye.config; + +import java.util.OptionalInt; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.common.annotation.Experimental; + +@Experimental("") +public interface ConfigSourceFactory { + ConfigSource getSource(ConfigSourceContext context); + + default OptionalInt getPriority() { + return OptionalInt.empty(); + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index f609f3638..feb8020d9 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -92,6 +92,11 @@ List discoverSources() { classLoader); configSourceProviderLoader.forEach(configSourceProvider -> configSourceProvider.getConfigSources(classLoader) .forEach(discoveredSources::add)); + + ServiceLoader configSourceFactoryLoader = ServiceLoader.load(ConfigSourceFactory.class, + classLoader); + configSourceFactoryLoader.forEach(this::withSources); + return discoveredSources; } @@ -186,6 +191,24 @@ public SmallRyeConfigBuilder withSources(Collection configSources) return this; } + public SmallRyeConfigBuilder withSources(ConfigSourceFactory... configSourceFactories) { + Stream.of(configSourceFactories).forEach(configSourceFactory -> { + interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() { + @Override + public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { + return SmallRyeConfigSourceInterceptor.configSourceInterceptor( + configSourceFactory.getSource(context::proceed)); + } + + @Override + public OptionalInt getPriority() { + return configSourceFactory.getPriority(); + } + })); + }); + return this; + } + public SmallRyeConfigBuilder withInterceptors(ConfigSourceInterceptor... interceptors) { this.interceptors.addAll(Stream.of(interceptors) .map(InterceptorWithPriority::new) diff --git a/implementation/src/test/java/io/smallrye/config/ConfigConfigSourceTest.java b/implementation/src/test/java/io/smallrye/config/ConfigConfigSourceTest.java new file mode 100644 index 000000000..cfbf39580 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/ConfigConfigSourceTest.java @@ -0,0 +1,94 @@ +package io.smallrye.config; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.stream.StreamSupport; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Test; + +import io.smallrye.config.common.AbstractConfigSource; + +public class ConfigConfigSourceTest { + @Test + public void configure() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultSources() + .addDefaultInterceptors() + .withSources(KeyValuesConfigSource.config("my.prop", "1234")) + .withSources(new ConfigSourceFactory() { + @Override + public ConfigSource getSource(final ConfigSourceContext context) { + return new AbstractConfigSource("test", 1000) { + final String value = context.getValue("my.prop").getValue(); + + @Override + public Map getProperties() { + return null; + } + + @Override + public String getValue(final String propertyName) { + return value; + } + }; + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(1000); + } + }) + .build(); + + final List configSources = StreamSupport.stream(config.getConfigSources().spliterator(), false) + .collect(toList()); + assertEquals(1, configSources.stream().filter(source -> source.getName().equals("test")).count()); + + assertEquals("1234", config.getRawValue("my.prop")); + assertEquals("test", config.getConfigValue("my.prop").getConfigSourceName()); + assertEquals("1234", config.getRawValue("any")); + assertEquals("test", config.getConfigValue("any").getConfigSourceName()); + } + + @Test + public void lowerPriority() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultSources() + .addDefaultInterceptors() + .withSources(KeyValuesConfigSource.config("my.prop", "1234")) + .withSources(new ConfigSourceFactory() { + @Override + public ConfigSource getSource(final ConfigSourceContext context) { + return new AbstractConfigSource("test", 0) { + final ConfigValue value = context.getValue("my.prop"); + + @Override + public Map getProperties() { + return null; + } + + @Override + public String getValue(final String propertyName) { + return value != null ? value.getValue() : null; + } + }; + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(0); + } + }) + .build(); + + assertEquals("1234", config.getRawValue("my.prop")); + assertEquals("KeyValuesConfigSource", config.getConfigValue("my.prop").getConfigSourceName()); + assertNull(config.getRawValue("any")); + } +} diff --git a/sources/zookeeper/pom.xml b/sources/zookeeper/pom.xml index a3d2a7638..a70acbc39 100644 --- a/sources/zookeeper/pom.xml +++ b/sources/zookeeper/pom.xml @@ -39,6 +39,11 @@ smallrye-config-common + + io.smallrye.config + smallrye-config + + org.apache.curator curator-recipes @@ -66,12 +71,6 @@ weld-junit5 - - io.smallrye.config - smallrye-config - test - - org.apache.curator curator-test @@ -84,7 +83,6 @@ - diff --git a/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigException.java b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigException.java index d8ee2ec6a..c756c58e2 100644 --- a/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigException.java +++ b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigException.java @@ -1,6 +1,6 @@ package io.smallrye.config.source.zookeeper; -public class ZooKeeperConfigException extends Exception { +public class ZooKeeperConfigException extends RuntimeException { public ZooKeeperConfigException() { super(); diff --git a/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSource.java b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSource.java index 5727e59ce..70668a3ad 100644 --- a/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSource.java +++ b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSource.java @@ -1,14 +1,11 @@ package io.smallrye.config.source.zookeeper; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.data.Stat; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import io.smallrye.config.common.AbstractConfigSource; @@ -23,26 +20,32 @@ public class ZooKeeperConfigSource extends AbstractConfigSource { private static final long serialVersionUID = 3127679154588598693L; - //Apache Curator framework used to access Zookeeper - private AtomicReference curatorReference = new AtomicReference<>(); - - //Root node of an application's configuration - private String applicationId; - - //Prefix of ignored properties - private static final String IGNORED_PREFIX = "io.smallrye.configsource.zookeeper"; - //Property the URL of the Zookeeper instance will be read from static final String ZOOKEEPER_URL_KEY = "io.smallrye.configsource.zookeeper.url"; - //Property of the Application Id. This will be the root znode for an application's properties static final String APPLICATION_ID_KEY = "io.smallrye.configsource.zookeeper.applicationId"; //Name of this ConfigSource private static final String ZOOKEEPER_CONFIG_SOURCE_NAME = "io.smallrye.configsource.zookeeper"; - public ZooKeeperConfigSource() { + //Apache Curator framework used to access Zookeeper + private final CuratorFramework curator; + //Root node of an application's configuration + private final String applicationId; + + public ZooKeeperConfigSource(final String zookeeperUrl, final String applicationId) { super(ZOOKEEPER_CONFIG_SOURCE_NAME, 150); + + //Only create the ZK Client if the properties exist. + if (zookeeperUrl != null && applicationId != null) { + ZooKeepperLogging.log.configuringZookeeper(zookeeperUrl, applicationId); + + this.applicationId = applicationId.startsWith("/") ? applicationId : "/" + applicationId; + this.curator = CuratorFrameworkFactory.newClient(zookeeperUrl, new ExponentialBackoffRetry(1000, 3)); + this.curator.start(); + } else { + throw ZooKeeperMessages.msg.propertiesNotSet(); + } } @Override @@ -51,7 +54,7 @@ public Set getPropertyNames() { final Set propertyNames = new HashSet<>(); try { - final List children = getCuratorClient().getChildren().forPath(applicationId); + final List children = curator.getChildren().forPath(applicationId); propertyNames.addAll(children); } catch (Exception e) { ZooKeepperLogging.log.failedToRetrievePropertyNames(e); @@ -66,9 +69,9 @@ public Map getProperties() { final Map props = new HashMap<>(); try { - final List children = getCuratorClient().getChildren().forPath(applicationId); + final List children = curator.getChildren().forPath(applicationId); for (final String key : children) { - final String value = new String(getCuratorClient().getData().forPath(applicationId + "/" + key)); + final String value = new String(curator.getData().forPath(applicationId + "/" + key)); props.put(key, value); } } catch (Exception e) { @@ -80,20 +83,11 @@ public Map getProperties() { @Override public String getValue(final String key) { - - /* - * Explicitly ignore all keys that are prefixed with the prefix used to configure the Zookeeper connection. - * Other wise a stack overflow obviously happens. - */ - // TODO - radcortez - We need to add a feature that allows ConfigSource to config itself with other ConfigSource - if (key.startsWith(IGNORED_PREFIX) || key.startsWith("smallrye.config")) { - return null; - } try { - final Stat stat = getCuratorClient().checkExists().forPath(applicationId + "/" + key); + final Stat stat = curator.checkExists().forPath(applicationId + "/" + key); if (stat != null) { - return new String(getCuratorClient().getData().forPath(applicationId + "/" + key)); + return new String(curator.getData().forPath(applicationId + "/" + key)); } else { return null; } @@ -102,35 +96,4 @@ public String getValue(final String key) { } return null; } - - private CuratorFramework getCuratorClient() throws ZooKeeperConfigException { - - CuratorFramework cachedClient = curatorReference.get(); - if (cachedClient == null) { - - final Config cfg = ConfigProvider.getConfig(); - - final Optional zookeeperUrl = cfg.getOptionalValue(ZOOKEEPER_URL_KEY, String.class); - final Optional optApplicationId = cfg.getOptionalValue(APPLICATION_ID_KEY, String.class); - - //Only create the ZK Client if the properties exist. - if (zookeeperUrl.isPresent() && optApplicationId.isPresent()) { - - ZooKeepperLogging.log.configuringZookeeper(zookeeperUrl.get(), optApplicationId.get()); - - applicationId = optApplicationId.get(); - - if (!applicationId.startsWith("/")) { - applicationId = "/" + applicationId; - } - cachedClient = CuratorFrameworkFactory.newClient(zookeeperUrl.get(), new ExponentialBackoffRetry(1000, 3)); - curatorReference.compareAndSet(null, cachedClient); - cachedClient.start(); - - } else { - throw ZooKeeperMessages.msg.propertiesNotSet(); - } - } - return cachedClient; - } } diff --git a/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSourceFactory.java b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSourceFactory.java new file mode 100644 index 000000000..1d23a9368 --- /dev/null +++ b/sources/zookeeper/src/main/java/io/smallrye/config/source/zookeeper/ZooKeeperConfigSourceFactory.java @@ -0,0 +1,21 @@ +package io.smallrye.config.source.zookeeper; + +import java.util.OptionalInt; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.config.ConfigSourceContext; +import io.smallrye.config.ConfigSourceFactory; + +public class ZooKeeperConfigSourceFactory implements ConfigSourceFactory { + @Override + public ConfigSource getSource(final ConfigSourceContext context) { + return new ZooKeeperConfigSource(context.getValue(ZooKeeperConfigSource.ZOOKEEPER_URL_KEY).getValue(), + context.getValue(ZooKeeperConfigSource.APPLICATION_ID_KEY).getValue()); + } + + @Override + public OptionalInt getPriority() { + return OptionalInt.of(150); + } +} diff --git a/sources/zookeeper/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory b/sources/zookeeper/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory new file mode 100644 index 000000000..ebc5feedb --- /dev/null +++ b/sources/zookeeper/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory @@ -0,0 +1 @@ +io.smallrye.config.source.zookeeper.ZooKeeperConfigSourceFactory diff --git a/sources/zookeeper/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/sources/zookeeper/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource deleted file mode 100644 index 419007878..000000000 --- a/sources/zookeeper/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.config.source.zookeeper.ZooKeeperConfigSource \ No newline at end of file