diff --git a/archaius2-core/src/main/java/com/netflix/archaius/ConfigMapper.java b/archaius2-core/src/main/java/com/netflix/archaius/ConfigMapper.java index af44f9e9f..7964b5206 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/ConfigMapper.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/ConfigMapper.java @@ -15,19 +15,18 @@ */ package com.netflix.archaius; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.lang3.text.StrSubstitutor; - import com.netflix.archaius.api.Config; import com.netflix.archaius.api.IoCContainer; import com.netflix.archaius.api.annotations.Configuration; import com.netflix.archaius.exceptions.MappingException; import com.netflix.archaius.interpolate.ConfigStrLookup; +import org.apache.commons.lang3.text.StrSubstitutor; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; public class ConfigMapper { private static final IoCContainer NULL_IOC_CONTAINER = new IoCContainer() { @@ -43,7 +42,6 @@ public T getInstance(String name, Class type) { * * @param injectee * @param config - * @param ioc * @throws MappingException */ public void mapConfig(T injectee, Config config) throws MappingException { @@ -111,18 +109,8 @@ public void mapConfig(T injectee, final Config config, IoCContainer ioc) thr String name = field.getName(); Class type = field.getType(); - Object value = null; - if (type.isInterface()) { - // TODO: Do Class.newInstance() if objName is a classname - String objName = config.getString(prefix + name, null); - if (objName != null) { - value = ioc.getInstance(objName, type); - } - } - else { - value = config.get(type, prefix + name, null); - } - + Object value = getValue(config, ioc, prefix, name, type); + if (value != null) { try { field.setAccessible(true); @@ -160,17 +148,8 @@ else if (name.startsWith("with") && name.length() > 4) { method.setAccessible(true); Class type = method.getParameterTypes()[0]; - Object value = null; - if (type.isInterface()) { - String objName = config.getString(prefix + name, null); - if (objName != null) { - value = ioc.getInstance(objName, type); - } - } - else { - value = config.get(type, prefix + name, null); - } - + Object value = getValue(config, ioc, prefix, name, type); + if (value != null) { try { method.invoke(injectee, value); @@ -190,4 +169,21 @@ else if (name.startsWith("with") && name.length() > 4) { } } } + + private Object getValue(Config config, IoCContainer ioc, String prefix, String name, Class type) { + if (type.isInterface()) { + Object rawProperty = config.getRawProperty(prefix + name); + if (rawProperty != null && type.isAssignableFrom(rawProperty.getClass())) { + return rawProperty; + } + + // TODO: Do Class.newInstance() if objName is a classname + String objName = config.getString(prefix + name, null); + if (objName != null) { + return ioc.getInstance(objName, type); + } + } + + return config.get(type, prefix + name, null); + } } diff --git a/archaius2-core/src/main/java/com/netflix/archaius/config/MapConfig.java b/archaius2-core/src/main/java/com/netflix/archaius/config/MapConfig.java index 7af034b39..6ce2bc334 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/config/MapConfig.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/config/MapConfig.java @@ -41,10 +41,10 @@ public class MapConfig extends AbstractConfig { * @author elandau */ public static class Builder { - Map map = new HashMap(); - + Map map = new HashMap(); + public Builder put(String key, T value) { - map.put(key, value.toString()); + map.put(key, value); return this; } @@ -60,19 +60,18 @@ public static Builder builder() { public static MapConfig from(Properties props) { return new MapConfig(props); } - - public static MapConfig from(Map props) { + + public static MapConfig from(Map props) { return new MapConfig(props); } - - private Map props = new HashMap(); - + + private Map props = new HashMap(); + /** * Construct a MapConfig as a copy of the provided Map - * @param name * @param props */ - public MapConfig(Map props) { + public MapConfig(Map props) { this.props.putAll(props); this.props = Collections.unmodifiableMap(this.props); } diff --git a/archaius2-core/src/main/java/com/netflix/archaius/config/PollingDynamicConfig.java b/archaius2-core/src/main/java/com/netflix/archaius/config/PollingDynamicConfig.java index f9482807e..d9ebaffdd 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/config/PollingDynamicConfig.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/config/PollingDynamicConfig.java @@ -40,7 +40,7 @@ public class PollingDynamicConfig extends AbstractConfig { private static final Logger LOG = LoggerFactory.getLogger(PollingDynamicConfig.class); - private volatile Map current = new HashMap(); + private volatile Map current = new HashMap(); private final AtomicBoolean busy = new AtomicBoolean(); private final Callable reader; private final AtomicLong updateCounter = new AtomicLong(); diff --git a/archaius2-core/src/main/java/com/netflix/archaius/config/polling/PollingResponse.java b/archaius2-core/src/main/java/com/netflix/archaius/config/polling/PollingResponse.java index c1a19b1db..b65fb5915 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/config/polling/PollingResponse.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/config/polling/PollingResponse.java @@ -5,10 +5,10 @@ import java.util.Map; public abstract class PollingResponse { - public static PollingResponse forSnapshot(final Map values) { + public static PollingResponse forSnapshot(final Map values) { return new PollingResponse() { @Override - public Map getToAdd() { + public Map getToAdd() { return values; } @@ -28,7 +28,7 @@ public boolean hasData() { public static PollingResponse noop() { return new PollingResponse() { @Override - public Map getToAdd() { + public Map getToAdd() { return Collections.emptyMap(); } @@ -44,7 +44,7 @@ public boolean hasData() { }; } - public abstract Map getToAdd(); + public abstract Map getToAdd(); public abstract Collection getToRemove(); public abstract boolean hasData(); } diff --git a/archaius2-core/src/main/java/com/netflix/archaius/readers/PropertiesConfigReader.java b/archaius2-core/src/main/java/com/netflix/archaius/readers/PropertiesConfigReader.java index e064da72a..b75b558ba 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/readers/PropertiesConfigReader.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/readers/PropertiesConfigReader.java @@ -64,13 +64,13 @@ private void internalLoad(Properties props, Set seenUrls, ClassLoader lo try { // Load properties into the single Properties object overriding any property // that may already exist - Map p = new URLConfigReader(url).call().getToAdd(); + Map p = new URLConfigReader(url).call().getToAdd(); LOG.debug("Loaded : {}", url.toExternalForm()); props.putAll(p); // Recursively load any files referenced by an @next property in the file // Only one @next property is expected and the value may be a list of files - String next = p.get(INCLUDE_KEY); + String next = (String) p.get(INCLUDE_KEY); if (next != null) { p.remove(INCLUDE_KEY); for (String urlString : next.split(",")) { diff --git a/archaius2-core/src/main/java/com/netflix/archaius/readers/URLConfigReader.java b/archaius2-core/src/main/java/com/netflix/archaius/readers/URLConfigReader.java index 32ec81e7b..6e526d227 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/readers/URLConfigReader.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/readers/URLConfigReader.java @@ -69,7 +69,7 @@ private static URL[] createUrls(String... urlStrings) { @Override public PollingResponse call() throws IOException { - final Map map = new HashMap(); + final Map map = new HashMap(); for (URL url: configUrls) { Properties props = new Properties(); InputStream fin = url.openStream(); @@ -95,7 +95,7 @@ public PollingResponse call() throws IOException { } return new PollingResponse() { @Override - public Map getToAdd() { + public Map getToAdd() { return map; } diff --git a/archaius2-core/src/test/java/com/netflix/archaius/config/PollingDynamicConfigTest.java b/archaius2-core/src/test/java/com/netflix/archaius/config/PollingDynamicConfigTest.java index eceefd690..febeffe9b 100644 --- a/archaius2-core/src/test/java/com/netflix/archaius/config/PollingDynamicConfigTest.java +++ b/archaius2-core/src/test/java/com/netflix/archaius/config/PollingDynamicConfigTest.java @@ -46,7 +46,7 @@ public void testBasicRead() throws Exception { server.getServerPathURI("/prop1").toURL() ); - Map result; + Map result; prop1.setProperty("a", "a_value"); result = reader.call().getToAdd(); @@ -72,7 +72,7 @@ public void testCombineSources() throws Exception { prop1.setProperty("a", "A"); prop2.setProperty("b", "B"); - Map result = reader.call().getToAdd(); + Map result = reader.call().getToAdd(); Assert.assertEquals(2, result.size()); Assert.assertEquals("A", result.get("a")); diff --git a/archaius2-guice/src/test/java/com/netflix/archaius/guice/ArchaiusModuleTest.java b/archaius2-guice/src/test/java/com/netflix/archaius/guice/ArchaiusModuleTest.java index 86a34b2d5..73fd7a7c5 100644 --- a/archaius2-guice/src/test/java/com/netflix/archaius/guice/ArchaiusModuleTest.java +++ b/archaius2-guice/src/test/java/com/netflix/archaius/guice/ArchaiusModuleTest.java @@ -15,13 +15,7 @@ */ package com.netflix.archaius.guice; -import java.util.Properties; - -import javax.inject.Inject; - -import org.junit.Assert; -import org.junit.Test; - +import com.google.common.collect.Lists; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -45,6 +39,13 @@ import com.netflix.archaius.config.MapConfig; import com.netflix.archaius.exceptions.MappingException; import com.netflix.archaius.visitor.PrintStreamVisitor; +import org.junit.Assert; +import org.junit.Test; + +import javax.inject.Inject; +import java.util.Date; +import java.util.List; +import java.util.Properties; public class ArchaiusModuleTest { @@ -63,6 +64,8 @@ public static class MyServiceConfig { private Boolean bool_value; private Double double_value; private Property fast_int; + private List int_list; + private Date date; private Named named; public void setStr_value(String value) { @@ -342,4 +345,27 @@ public TestProxyConfig getProxyConfig(ConfigProxyFactory factory) { Assert.assertArrayEquals(new String[]{"foo", "bar"}, configProxy.getStringArray()); Assert.assertArrayEquals(new Integer[]{1,2}, configProxy.getIntArray()); } + + @Test + public void testObjectInjection() throws MappingException { + Date date = new Date(100); + final Config config = MapConfig.builder() + .put("env", "prod") + .put("prefix-prod.int_list", Lists.newArrayList(1, 2, 3)) + .put("prefix-prod.date", date) + .build(); + + Injector injector = Guice.createInjector( + new ArchaiusModule() { + @Override + protected void configureArchaius() { + bindApplicationConfigurationOverride().toInstance(config); + } + }); + + MyServiceConfig serviceConfig = injector.getInstance(MyServiceConfig.class); + Assert.assertEquals(Lists.newArrayList(1, 2, 3), serviceConfig.int_list); + Assert.assertEquals(date, serviceConfig.date); + } + } diff --git a/archaius2-persisted2/src/main/java/com/netflix/archaius/persisted2/JsonPersistedV2Reader.java b/archaius2-persisted2/src/main/java/com/netflix/archaius/persisted2/JsonPersistedV2Reader.java index 8800cb23a..deca0f70f 100644 --- a/archaius2-persisted2/src/main/java/com/netflix/archaius/persisted2/JsonPersistedV2Reader.java +++ b/archaius2-persisted2/src/main/java/com/netflix/archaius/persisted2/JsonPersistedV2Reader.java @@ -187,7 +187,7 @@ public PollingResponse call() throws Exception { } // Resolve to a single property value - final Map result = new HashMap(); + final Map result = new HashMap(); for (Entry> entry : props.entrySet()) { result.put(entry.getKey(), valueResolver.resolve(entry.getKey(), entry.getValue())); } diff --git a/archaius2-typesafe/build.gradle b/archaius2-typesafe/build.gradle index f0176d0aa..577fe64db 100644 --- a/archaius2-typesafe/build.gradle +++ b/archaius2-typesafe/build.gradle @@ -18,9 +18,14 @@ apply plugin: 'java' dependencies { compile project(':archaius2-core') - compile 'com.typesafe:config:1.2.1' + compile 'com.typesafe:config:1.3.0' + + testCompile project(':archaius2-guice') } +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + eclipse { classpath { downloadSources = true diff --git a/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/DefaultTypesafeClientConfig.java b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/DefaultTypesafeClientConfig.java new file mode 100644 index 000000000..73ae697b4 --- /dev/null +++ b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/DefaultTypesafeClientConfig.java @@ -0,0 +1,79 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigParseOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.function.Supplier; + +public class DefaultTypesafeClientConfig implements TypesafeClientConfig { + private final Logger LOG = LoggerFactory.getLogger(DefaultTypesafeClientConfig.class); + private final String configFilePath; + private final int refreshIntervalMs; + + public DefaultTypesafeClientConfig(Builder builder) { + this.configFilePath = builder.configFilePath; + this.refreshIntervalMs = builder.refreshIntervalMs; + } + + public static class Builder { + private String configFilePath; + private int refreshIntervalMs; + + public Builder withConfigFilePath(String configFilePath) { + this.configFilePath = configFilePath; + return this; + } + + public Builder withRefreshIntervalMs(int refreshIntervalMs) { + this.refreshIntervalMs = refreshIntervalMs; + return this; + } + + public DefaultTypesafeClientConfig build() { + return new DefaultTypesafeClientConfig(this); + } + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public int getRefreshRateMs() { + return refreshIntervalMs; + } + + @Override + public String getTypesafeConfigPath() { + return configFilePath; + } + + @Override + public Supplier getTypesafeConfigSupplier() { + return new Supplier() { + @Override + public Config get() { + String typesafeConfigPath = getTypesafeConfigPath(); + LOG.info("Loading typesafe config from: {}", typesafeConfigPath); + try { + return ConfigFactory.parseURL(new URL(typesafeConfigPath), + ConfigParseOptions.defaults().setAllowMissing(false)).resolve(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + }; + } + + @Override + public String toString() { + return "DefaultTypesafeClientConfig { configFilePath: " + configFilePath + + ", refreshIntervalMs: " + refreshIntervalMs + " }"; + } +} diff --git a/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeClientConfig.java b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeClientConfig.java new file mode 100644 index 000000000..8d29374e3 --- /dev/null +++ b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeClientConfig.java @@ -0,0 +1,31 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.typesafe.config.Config; + +import java.util.function.Supplier; + +/** + * Provides information on how to retrieve dynamic typesafe configs. + */ +public interface TypesafeClientConfig { + /** + * @return True if the client is enabled. This is checked only once at startup. + */ + boolean isEnabled(); + + /** + * @return Polling rate for getting updates + */ + int getRefreshRateMs(); + + String getTypesafeConfigPath(); + + /** + * + * It's up to the user to decide what typesafe Config they want to inject in archaius. + * This method returns a Supplier. The Supplier.get() will be called on every polling iteration in order + * to retrieve the latest typesafe config. + * + */ + Supplier getTypesafeConfigSupplier(); +} \ No newline at end of file diff --git a/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigLoader.java b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigLoader.java new file mode 100644 index 000000000..d01d13de5 --- /dev/null +++ b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigLoader.java @@ -0,0 +1,95 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.netflix.archaius.config.polling.PollingResponse; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigValue; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +/** + * Loads typesafe configs. + */ +public class TypesafeConfigLoader implements Callable { + private final Logger LOG = LoggerFactory.getLogger(TypesafeConfigLoader.class); + private Supplier typesafeConfigSupplier; + + public TypesafeConfigLoader(Supplier typesafeConfigSupplier) { + this.typesafeConfigSupplier = typesafeConfigSupplier; + } + + public PollingResponse call() throws Exception { + final Map map = new HashMap<>(); + + Config typesafeConfig = typesafeConfigSupplier.get(); + + for (Map.Entry entry : typesafeConfig.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue().unwrapped(); + + if (addAsUnit(map, typesafeConfig, key, value)) continue; + + map.put(key, value); + } + + return new PollingResponse() { + @Override + public Map getToAdd() { + return map; + } + + @Override + public Collection getToRemove() { + return Collections.emptyList(); + } + + @Override + public boolean hasData() { + return map.size() > 0; + } + }; + } + + /** + * Tries to parse the value to a Duration/ConfigMemorySize and add it as that to the map. + */ + private boolean addAsUnit(Map map, Config typesafeConfig, String key, Object value) { + if (value instanceof String) { + String v = (String) value; + if (StringUtils.isNotBlank(v) && !Character.isDigit(v.charAt(v.length() - 1))) { + // We're dealing with a unit. Try to parse it to Duration/ConfigMemorySize. + Optional parsedValue = getAsUnit(typesafeConfig, key); + if (parsedValue.isPresent()) { + map.put(key, parsedValue.get()); + return true; + } + } + } + return false; + } + + private Optional getAsUnit(Config typesafeConfig, String key) { + try { + return Optional.of(typesafeConfig.getDuration(key)); + } catch (Exception e) { + // could not parse value as Duration. + } + + try { + return Optional.of(typesafeConfig.getMemorySize(key)); + } catch (Exception e) { + // could not parse value as MemorySize. + } + + return Optional.empty(); + } + +} diff --git a/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigProvider.java b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigProvider.java new file mode 100644 index 000000000..9086583e0 --- /dev/null +++ b/archaius2-typesafe/src/main/java/com/netflix/archaius/typesafe/dynamic/TypesafeConfigProvider.java @@ -0,0 +1,80 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.netflix.archaius.api.Config; +import com.netflix.archaius.config.EmptyConfig; +import com.netflix.archaius.config.PollingDynamicConfig; +import com.netflix.archaius.config.polling.FixedPollingStrategy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import java.util.concurrent.TimeUnit; + +/** + * Provider that sets up a Config which is backed up by a typesafe loader. + * Once injected the Config will poll the service for updated on a configurable + * interval. Note that injection of this Config will block until the first + * set of properties has been fetched from the typesafe service. + * + * The provider must be bound to a specific config layer within the override + * hierarchy. + * + * For example, TypesafeClientConfig may bound to the OverrideLayer like this, + *
+ *   final TypesafeClientConfig config = new DefaultTypesafeClientConfig.Builder()
+ *          .withConfigFilePath("http://example.com/typesafe/reference.conf") // or file:///tmp/typesafe/reference.conf
+ *          .withRefreshIntervalMs(60 * 1000)
+ *          .build();
+ *
+ *   Guice.createInjector(
+ *      Modules
+ *          .override(new ArchaiusModule())
+ *          .with(new AbstractModule() {
+ *              @Override
+ *              protected void configureArchaius() {
+ *                  bind(TypesafeClientConfig.class).toInstance(config);
+ *                  bind(Config.class).annotatedWith(OverrideLayer.class).toProvider(TypesafeConfigProvider.class).in(Scopes.SINGLETON);
+ *                  // or bind to RemoteLayer
+ *                  // bind(Config.class).annotatedWith(RemoteLayer.class).toProvider(TypesafeConfigProvider.class).in(Scopes.SINGLETON);
+ *              }
+ *          })
+ *      )
+ * 
+ * + */ +public class TypesafeConfigProvider implements Provider { + private final Logger LOG = LoggerFactory.getLogger(TypesafeConfigProvider.class); + + private volatile PollingDynamicConfig dynamicConfig; + private TypesafeClientConfig clientConfig; + + @Inject + public TypesafeConfigProvider(TypesafeClientConfig clientConfig) throws Exception { + this.clientConfig = clientConfig; + } + + public Config get() { + if (!clientConfig.isEnabled()) { + LOG.info("Typesafe config is not enabled."); + return EmptyConfig.INSTANCE; + } + + try { + return dynamicConfig = new PollingDynamicConfig(new TypesafeConfigLoader(clientConfig.getTypesafeConfigSupplier()), + new FixedPollingStrategy(clientConfig.getRefreshRateMs(), TimeUnit.MILLISECONDS)); + } catch (Exception e) { + LOG.error("Unable to initialize the dynamic typesafe config", e); + throw new RuntimeException(e); + } + } + + @PreDestroy + public void shutdown() { + LOG.info("Shutting down TypesafeConfigProvider: {}.", toString()); + if (dynamicConfig != null) { + dynamicConfig.shutdown(); + } + } +} \ No newline at end of file diff --git a/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/DynamicTypesafeConfigTest.java b/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/DynamicTypesafeConfigTest.java new file mode 100644 index 000000000..2ed03dda3 --- /dev/null +++ b/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/DynamicTypesafeConfigTest.java @@ -0,0 +1,114 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.google.common.collect.Lists; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Scopes; +import com.netflix.archaius.api.Config; +import com.netflix.archaius.api.exceptions.ConfigException; +import com.netflix.archaius.api.inject.RemoteLayer; +import com.netflix.archaius.guice.ArchaiusModule; +import com.typesafe.config.ConfigFactory; +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; + +public class DynamicTypesafeConfigTest { + + @Test + public void test() throws ConfigException { + final TypesafeClientConfig config = new DefaultTypesafeClientConfig.Builder() + .withConfigFilePath(getResourceUrl("dynamic/reference.conf")) + .withRefreshIntervalMs(100) + .build(); + + Injector injector = Guice.createInjector( + new ArchaiusModule() { + @Override + protected void configureArchaius() { + bind(TypesafeClientConfig.class).toInstance(config); + bind(Config.class) + .annotatedWith(RemoteLayer.class) + .toProvider(TypesafeConfigProvider.class) + .in(Scopes.SINGLETON); + } + }); + + SampleAppConfig appConfig = injector.getInstance(SampleAppConfig.class); + + assertEquals("app name", appConfig.name); + assertEquals(true, appConfig.flag); + assertEquals(111, appConfig.number.intValue()); + assertEquals(Lists.newArrayList(1, 2), appConfig.list); + assertEquals(300, appConfig.size.toBytes()); + assertEquals(500, appConfig.duration.toMillis()); + } + + @Test + public void testDynamicConfigChange() throws ConfigException, InterruptedException { + String[] configPath = new String[]{"dynamic/reference.conf"}; + + final TypesafeClientConfig config = new TypesafeClientConfig() { + @Override + public boolean isEnabled() { + return true; + } + + @Override + public int getRefreshRateMs() { + return 200; + } + + @Override + public String getTypesafeConfigPath() { + return configPath[0]; + } + + @Override + public Supplier getTypesafeConfigSupplier() { + return () -> { + String typesafeConfigPath = getTypesafeConfigPath(); + System.out.println("Load from: " + typesafeConfigPath); + return ConfigFactory.load(typesafeConfigPath); + }; + } + }; + + Injector injector = Guice.createInjector( + new ArchaiusModule() { + @Override + protected void configureArchaius() { + bind(TypesafeClientConfig.class).toInstance(config); + bind(Config.class) + .annotatedWith(RemoteLayer.class) + .toProvider(TypesafeConfigProvider.class) + .in(Scopes.SINGLETON); + } + }); + + SampleAppConfig appConfig = injector.getInstance(SampleAppConfig.class); + assertEquals("app name", appConfig.name); + assertEquals(true, appConfig.flag); + assertEquals(111, appConfig.number.intValue()); + assertEquals(Lists.newArrayList(1, 2), appConfig.list); + + // Simulate configuration update at runtime. + configPath[0] = "dynamic/updated.conf"; + Thread.sleep(400); + + SampleAppConfig updatedAppConfig = injector.getInstance(SampleAppConfig.class); + assertEquals("updated app name", updatedAppConfig.name); + assertEquals(false, updatedAppConfig.flag); + assertEquals(222, updatedAppConfig.number.intValue()); + assertEquals(Lists.newArrayList(3, 4), updatedAppConfig.list); + // Removed keys in the updated config. + assertEquals(null, updatedAppConfig.duration); + assertEquals(null, updatedAppConfig.size); + } + + private String getResourceUrl(String resourceName) { + return "file://" + getClass().getClassLoader().getResource(resourceName).getPath(); + } +} diff --git a/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/SampleAppConfig.java b/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/SampleAppConfig.java new file mode 100644 index 000000000..4fc3c86c5 --- /dev/null +++ b/archaius2-typesafe/src/test/java/com/netflix/archaius/typesafe/dynamic/SampleAppConfig.java @@ -0,0 +1,18 @@ +package com.netflix.archaius.typesafe.dynamic; + +import com.netflix.archaius.api.annotations.Configuration; +import com.typesafe.config.ConfigMemorySize; + +import java.time.Duration; +import java.util.List; + +@Configuration(prefix = "app", allowFields = true) +public class SampleAppConfig { + + public String name; + public Boolean flag; + public Integer number; + public List list; + public Duration duration; + public ConfigMemorySize size; +} diff --git a/archaius2-typesafe/src/test/resources/dynamic/reference.conf b/archaius2-typesafe/src/test/resources/dynamic/reference.conf new file mode 100644 index 000000000..71c75d1b2 --- /dev/null +++ b/archaius2-typesafe/src/test/resources/dynamic/reference.conf @@ -0,0 +1,9 @@ +app { + name: "app name" + flag: true + number: 111 + + list: [1,2] + duration: 500ms + size: 300bytes +} \ No newline at end of file diff --git a/archaius2-typesafe/src/test/resources/dynamic/updated.conf b/archaius2-typesafe/src/test/resources/dynamic/updated.conf new file mode 100644 index 000000000..f0b8a7ddc --- /dev/null +++ b/archaius2-typesafe/src/test/resources/dynamic/updated.conf @@ -0,0 +1,6 @@ +app { + name: "updated app name" + flag: false + number: 222 + list: [3,4] +} \ No newline at end of file