diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index f9b223af0..d70219de0 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -2,9 +2,10 @@ * xref:config/config.adoc[Config] +* xref:config-sources/config-sources.adoc[Config Sources] + +* xref:converters/converters.adoc[Converters] + * xref:interceptors/interceptors.adoc[Interceptors] -* Extensions -** xref:config-sources/config-sources.adoc[Config Sources] -** xref:converters/converters.adoc[Converters] -** xref:cdi/cdi.adoc[CDI] +* xref:cdi/cdi.adoc[CDI] diff --git a/doc/modules/ROOT/pages/config-sources/config-sources.adoc b/doc/modules/ROOT/pages/config-sources/config-sources.adoc index 39482df91..b3c5e606b 100644 --- a/doc/modules/ROOT/pages/config-sources/config-sources.adoc +++ b/doc/modules/ROOT/pages/config-sources/config-sources.adoc @@ -5,6 +5,28 @@ include::../attributes.adoc[] = Config Sources +SmallRye Config provides a few enhancements and extensions to MicroProfile Config `ConfigSource`. + +== Config Source Factory + +Another way to create a `ConfigSource` is via the `ConfigSourceFactory`. The difference between the SmallRye Factory +and the standard way to create a `ConfigSource` as specified in MicroProfile Config, is the Factory ability to provide +a context with access to the available configuration. + +By implementing https://github.com/smallrye/smallrye-config/blob/master/implementation/src/main/java/io/smallrye/config/ConfigSourceFactoryjava[ConfigSourceFactory], the ConfigSource is made available via the +`ConfigSource getSource(ConfigSourceContext context)` method. The `ConfigSourceFactory` can be also assigned a priority, +like any other `ConfigSource` by overriding the method `OptionalInt getPriority()`. + +When the Factory is initializing, a `ConfigSourceContext` is provided, that allows to call a single method +`ConfigValue getValue(String name)`. This method lookups configurations names in all `ConfigSources` that are already +initialized by the `Config`. These are the `ConfigSources` with higher priority than the Factory. With the +`ConfigSourceFactory` it is possible to bootstrap a `ConfigSource` that configures itself with other `ConfigSources`. + +Registration of a `ConfigSourceFactory` is done via the `ServiceLoader` mechanism by providing the +implementation classes in a `META-INF/services/io.smallrye.config.ConfigSourceFactory` file. Alternatively, factories +may be registered via the Programmatic API in `SmallRyeConfigBuilder#withSources`. + +== Available Config Sources In addition to the default Config Sources specified by MicroProfile Config (Environment Variables, System Properties and `microprofile-config.properties` file), SmallRye Config provides the following additional Sources: 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..1de649778 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceContext.java @@ -0,0 +1,11 @@ +package io.smallrye.config; + +import io.smallrye.common.annotation.Experimental; + +/** + * Exposes contextual information on the ConfigSource initialization via {@link ConfigSourceFactory}. + */ +@Experimental("ConfigSource API Enhancements") +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..3179ad97a --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceFactory.java @@ -0,0 +1,31 @@ +package io.smallrye.config; + +import java.util.OptionalInt; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.common.annotation.Experimental; + +/** + * This ConfigSourceFactory allows to initialize a {@link ConfigSource}, with access to the current + * {@link ConfigSourceContext}. + *

+ * + * The provided {@link ConfigSource} is initialized in priority order and the current {@link ConfigSourceContext} has + * access to all previous initialized {@code ConfigSources}. This allows the factory to configure the + * {@link ConfigSource} with configurations read from higher priority {@code ConfigSources}. + *

+ * + * Instances of this interface will be discovered by {@link SmallRyeConfigBuilder#withSources(ConfigSourceFactory...)} + * via the {@link java.util.ServiceLoader} mechanism and can be registered by providing a + * {@code META-INF/services/io.smallrye.config.ConfigSourceFactory} which contains the fully qualified class name of the + * custom {@link ConfigSourceFactory} implementation. + */ +@Experimental("ConfigSource API Enhancements") +public interface ConfigSourceFactory { + ConfigSource getSource(ConfigSourceContext context); + + default OptionalInt getPriority() { + return OptionalInt.empty(); + } +} diff --git a/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java index 061309766..db0ad34d3 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorFactory.java @@ -14,11 +14,10 @@ * initialized. *

* - * Instances of this interface will be discovered by {@code SmallRyeConfigBuilder#addDiscoveredInterceptors()} via the + * Instances of this interface will be discovered by {@link SmallRyeConfigBuilder#addDiscoveredInterceptors()} via the * {@link java.util.ServiceLoader} mechanism and can be registered by providing a - * {@code META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory} - * {@linkplain ClassLoader#getResource(String) resource} which contains the fully qualified class name of the - * custom {@code ConfigSourceProvider} implementation. + * {@code META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory} which contains the fully qualified class + * name of the custom {@link ConfigSourceInterceptor} implementation. */ @Experimental("Interceptor API to intercept resolution of a configuration name") public interface ConfigSourceInterceptorFactory { diff --git a/implementation/src/main/java/io/smallrye/config/ConfigValueConfigSourceWrapper.java b/implementation/src/main/java/io/smallrye/config/ConfigValueConfigSourceWrapper.java index 34df75c17..5865f4fdc 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigValueConfigSourceWrapper.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigValueConfigSourceWrapper.java @@ -62,6 +62,10 @@ public int getOrdinal() { return configSource.getOrdinal(); } + ConfigSource unwrap() { + return configSource; + } + static ConfigValueConfigSource wrap(final ConfigSource configSource) { if (configSource instanceof ConfigValueConfigSource) { return (ConfigValueConfigSource) configSource; diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index caae5b254..60676b042 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -17,6 +17,7 @@ import static io.smallrye.config.ConfigSourceInterceptor.EMPTY; import static io.smallrye.config.SmallRyeConfigSourceInterceptor.configSourceInterceptor; +import static java.util.stream.Collectors.toList; import java.io.Serializable; import java.lang.reflect.Type; @@ -48,20 +49,8 @@ * @author Jeff Mesnil (c) 2017 Red Hat inc. */ public class SmallRyeConfig implements Config, Serializable { - private static final long serialVersionUID = 8138651532357898263L; - static final Comparator CONFIG_SOURCE_COMPARATOR = new Comparator() { - @Override - public int compare(ConfigSource o1, ConfigSource o2) { - int res = Integer.compare(o2.getOrdinal(), o1.getOrdinal()); - // if 2 config sources have the same ordinal, - // provide consistent order by sorting them - // according to their name. - return res != 0 ? res : o2.getName().compareTo(o1.getName()); - } - }; - private final AtomicReference configSources; private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); @@ -229,14 +218,7 @@ public Iterable getConfigSources() { */ @Deprecated public void addConfigSource(ConfigSource configSource) { - configSources.updateAndGet(configSources -> { - List currentSources = configSources.getSources(); - - int oldSize = currentSources.size(); - List newSources = Arrays.asList(currentSources.toArray(new ConfigSource[oldSize + 1])); - newSources.set(oldSize, configSource); - return new ConfigSources(newSources, configSources); - }); + configSources.updateAndGet(configSources -> new ConfigSources(configSources, configSource)); } public T convert(String value, Class asType) { @@ -270,7 +252,7 @@ private static class ConfigSources implements Serializable { private static final long serialVersionUID = 3483018375584151712L; private final List sources; - private final List interceptors; + private final List interceptors; private final ConfigSourceInterceptorContext interceptorChain; /** @@ -285,23 +267,21 @@ private static class ConfigSources implements Serializable { * @param interceptors the Interceptors to be part of Config. */ ConfigSources(final List sources, final List interceptors) { - sources.sort(CONFIG_SOURCE_COMPARATOR); - interceptors.sort(Comparator.comparingInt(InterceptorWithPriority::getPriority).reversed()); + final List sortInterceptors = new ArrayList<>(); + sortInterceptors.addAll(sources.stream().map(ConfigSourceInterceptorWithPriority::new).collect(toList())); + sortInterceptors.addAll(interceptors.stream().map(ConfigSourceInterceptorWithPriority::new).collect(toList())); + sortInterceptors.sort(Comparator.comparingInt(ConfigSourceInterceptorWithPriority::getPriority)); - List initializedInterceptors = new ArrayList<>(); + final List initInterceptors = new ArrayList<>(); SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); - for (int i = sources.size() - 1; i >= 0; i--) { - current = new SmallRyeConfigSourceInterceptorContext(configSourceInterceptor(sources.get(i)), current); - } - - for (int i = interceptors.size() - 1; i >= 0; i--) { - ConfigSourceInterceptor interceptor = interceptors.get(i).getInterceptor(current); - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); - initializedInterceptors.add(interceptor); + for (ConfigSourceInterceptorWithPriority configSourceInterceptor : sortInterceptors) { + final ConfigSourceInterceptorWithPriority initInterceptor = configSourceInterceptor.getInterceptor(current); + current = new SmallRyeConfigSourceInterceptorContext(initInterceptor.getInterceptor(), current); + initInterceptors.add(initInterceptor); } - this.sources = Collections.unmodifiableList(sources); - this.interceptors = Collections.unmodifiableList(initializedInterceptors); + this.sources = Collections.unmodifiableList(getSources(initInterceptors)); + this.interceptors = Collections.unmodifiableList(initInterceptors); this.interceptorChain = current; } @@ -311,31 +291,42 @@ private static class ConfigSources implements Serializable { * Config Sources are added to the Config. * * @param sources the Config Sources to be part of Config. - * @param configSources the previous ConfigSources + * @param configSource the new ConfigSource to add into the interceptor the chain. */ - ConfigSources(final List sources, final ConfigSources configSources) { - sources.sort(CONFIG_SOURCE_COMPARATOR); + ConfigSources(final ConfigSources sources, final ConfigSource configSource) { + final int oldSize = sources.getInterceptors().size(); + final List newInterceptors = Arrays + .asList(sources.getInterceptors().toArray(new ConfigSourceInterceptorWithPriority[oldSize + 1])); + newInterceptors.set(oldSize, new ConfigSourceInterceptorWithPriority(configSource)); + newInterceptors.sort(Comparator.comparingInt(ConfigSourceInterceptorWithPriority::getPriority)); SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); - - for (int i = sources.size() - 1; i >= 0; i--) { - current = new SmallRyeConfigSourceInterceptorContext(configSourceInterceptor(sources.get(i)), current); - } - - for (int i = configSources.getInterceptors().size() - 1; i >= 0; i--) { - current = new SmallRyeConfigSourceInterceptorContext(configSources.getInterceptors().get(i), current); + for (ConfigSourceInterceptorWithPriority configSourceInterceptor : newInterceptors) { + final ConfigSourceInterceptor interceptor = configSourceInterceptor.getInterceptor(); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); } - this.sources = Collections.unmodifiableList(sources); - this.interceptors = configSources.getInterceptors(); + this.sources = Collections.unmodifiableList(getSources(newInterceptors)); + this.interceptors = Collections.unmodifiableList(newInterceptors); this.interceptorChain = current; } + private List getSources(final List interceptors) { + final List sources = interceptors.stream() + .map(ConfigSourceInterceptorWithPriority::getInterceptor) + .filter(SmallRyeConfigSourceInterceptor.class::isInstance) + .map(SmallRyeConfigSourceInterceptor.class::cast) + .map(SmallRyeConfigSourceInterceptor::getSource) + .collect(toList()); + Collections.reverse(sources); + return sources; + } + List getSources() { return sources; } - List getInterceptors() { + List getInterceptors() { return interceptors; } @@ -343,4 +334,49 @@ ConfigSourceInterceptorContext getInterceptorChain() { return interceptorChain; } } + + static class ConfigSourceInterceptorWithPriority implements Comparable, Serializable { + private static final long serialVersionUID = 1637460029437579033L; + + final Function interceptor; + final int priority; + final String name; + + public ConfigSourceInterceptorWithPriority(final InterceptorWithPriority interceptor) { + this.interceptor = interceptor::getInterceptor; + this.priority = interceptor.getPriority(); + this.name = "undefined"; + } + + public ConfigSourceInterceptorWithPriority(final ConfigSource configSource) { + this.interceptor = context -> configSourceInterceptor(configSource); + this.priority = configSource.getOrdinal(); + this.name = configSource.getName(); + } + + public ConfigSourceInterceptorWithPriority(final ConfigSourceInterceptor interceptor, final int priority, + final String name) { + this.interceptor = (Serializable & Function) context -> interceptor; + this.priority = priority; + this.name = name; + } + + public ConfigSourceInterceptor getInterceptor() { + return interceptor.apply(null); + } + + public ConfigSourceInterceptorWithPriority getInterceptor(final ConfigSourceInterceptorContext context) { + return new ConfigSourceInterceptorWithPriority(interceptor.apply(context), priority, name); + } + + public int getPriority() { + return priority; + } + + @Override + public int compareTo(final ConfigSourceInterceptorWithPriority other) { + int res = Integer.compare(this.priority, other.priority); + return res != 0 ? res : this.name.compareTo(other.name); + } + } } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index e57f2b321..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) @@ -315,7 +338,8 @@ Converter getConverter() { } static class InterceptorWithPriority { - static final OptionalInt OPTIONAL_DEFAULT_PRIORITY = OptionalInt.of(ConfigSourceInterceptorFactory.DEFAULT_PRIORITY); + private static final OptionalInt OPTIONAL_DEFAULT_PRIORITY = OptionalInt + .of(ConfigSourceInterceptorFactory.DEFAULT_PRIORITY); private final ConfigSourceInterceptorFactory factory; private final int priority; @@ -346,11 +370,11 @@ private InterceptorWithPriority(ConfigSourceInterceptorFactory factory) { this.priority = factory.getPriority().orElse(ConfigSourceInterceptorFactory.DEFAULT_PRIORITY); } - public ConfigSourceInterceptor getInterceptor(ConfigSourceInterceptorContext context) { + ConfigSourceInterceptor getInterceptor(ConfigSourceInterceptorContext context) { return factory.getInterceptor(context); } - public int getPriority() { + int getPriority() { return priority; } } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptor.java index 838fffbb0..fd52bc665 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptor.java @@ -43,6 +43,14 @@ public Iterator iterateValues(final ConfigSourceInterceptorContext return values.iterator(); } + public ConfigSource getSource() { + if (configSource instanceof ConfigValueConfigSourceWrapper) { + return ((ConfigValueConfigSourceWrapper) configSource).unwrap(); + } + + return configSource; + } + public static ConfigSourceInterceptor configSourceInterceptor(final ConfigSource configSource) { return new SmallRyeConfigSourceInterceptor(configSource); } 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/implementation/src/test/java/io/smallrye/config/ConfigSourceSortTestCase.java b/implementation/src/test/java/io/smallrye/config/ConfigSourceSortTestCase.java deleted file mode 100644 index 41d910ea6..000000000 --- a/implementation/src/test/java/io/smallrye/config/ConfigSourceSortTestCase.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2019 Red Hat, Inc., and individual contributors - * as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.smallrye.config; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Comparator; -import java.util.Map; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.junit.jupiter.api.Test; - -/** - * - */ -public class ConfigSourceSortTestCase { - - static final Comparator CMP = SmallRyeConfig.CONFIG_SOURCE_COMPARATOR; - - static final class DummyConfigSource implements ConfigSource { - private final int ordinal; - - DummyConfigSource(final int ordinal) { - this.ordinal = ordinal; - } - - public int getOrdinal() { - return ordinal; - } - - public Map getProperties() { - return null; - } - - public String getValue(final String propertyName) { - return null; - } - - public String getName() { - return "placeholder"; - } - } - - static ConfigSource src(int ordinal) { - return new DummyConfigSource(ordinal); - } - - @Test - public void ensureCorrectOrdinalSort() { - assertEquals(1, CMP.compare(src(100), src(200))); - assertEquals(1, CMP.compare(src(-100), src(200))); - assertEquals(0, CMP.compare(src(0), src(0))); - assertEquals(0, CMP.compare(src(-100), src(-100))); - assertEquals(1, CMP.compare(src(0), src(Integer.MAX_VALUE))); - assertEquals(1, CMP.compare(src(Integer.MIN_VALUE), src(0))); - assertEquals(-1, CMP.compare(src(0), src(Integer.MIN_VALUE))); - assertEquals(-1, CMP.compare(src(Integer.MAX_VALUE), src(0))); - } -} diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSourceWrapperTestCase.java b/implementation/src/test/java/io/smallrye/config/ConfigSourceWrapperTestCase.java index f66591562..b0835c669 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigSourceWrapperTestCase.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigSourceWrapperTestCase.java @@ -105,6 +105,11 @@ public String getValue(final String propertyName) { public String getName() { return delegate.getName(); } + + @Override + public int getOrdinal() { + return delegate.getOrdinal(); + } } static T assertIsInstance(Class expected, Object thing) { 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