diff --git a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java
index e99ee1d..8660d1d 100644
--- a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java
+++ b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java
@@ -64,8 +64,8 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
return new MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/json; charset=utf-8")
- .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA/toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"},"
- + "{\"CreateIndex\":2,\"ModifyIndex\":2,\"LockIndex\":0,\"Key\":\"us-west-2/featureA/toggle\",\"Flags\":0,\"Value\":\""
+ .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA.toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"},"
+ + "{\"CreateIndex\":2,\"ModifyIndex\":2,\"LockIndex\":0,\"Key\":\"us-west-2/featureA.toggle\",\"Flags\":0,\"Value\":\""
+ (usWest2Toggle ? enabledBase64 : disabledBase64) + "\"}]");
}
return new MockResponse().setResponseCode(404);
diff --git a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java
new file mode 100644
index 0000000..c91cccd
--- /dev/null
+++ b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl)
+ *
+ * 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 org.cfg4j.source.consul;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.squareup.okhttp.mockwebserver.Dispatcher;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import org.cfg4j.provider.ConfigurationProvider;
+import org.cfg4j.provider.ConfigurationProviderBuilder;
+import org.cfg4j.source.ConfigurationSource;
+import org.cfg4j.source.context.environment.ImmutableEnvironment;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SimpleConfigurationProviderIntegrationTest {
+
+ private static final String PING_RESPONSE = "\n" +
+ "{\"Config\":{\"Bootstrap\":true,\"BootstrapExpect\":0,\"Server\":true,\"Datacenter\":\"dc1\",\"DataDir\":\"/tmp/consul\",\"DNSRecursor\":\"\",\"DNSRecursors\":[],\"DNSConfig\":{\"NodeTTL\":0,\"ServiceTTL\":null,\"AllowStale\":false,\"EnableTruncate\":false,\"MaxStale\":5000000000,\"OnlyPassing\":false},\"Domain\":\"consul.\",\"LogLevel\":\"INFO\",\"NodeName\":\"receivehead-lm\",\"ClientAddr\":\"127.0.0.1\",\"BindAddr\":\"0.0.0.0\",\"AdvertiseAddr\":\"192.168.0.4\",\"Ports\":{\"DNS\":8600,\"HTTP\":8500,\"HTTPS\":-1,\"RPC\":8400,\"SerfLan\":8301,\"SerfWan\":8302,\"Server\":8300},\"Addresses\":{\"DNS\":\"\",\"HTTP\":\"\",\"HTTPS\":\"\",\"RPC\":\"\"},\"LeaveOnTerm\":false,\"SkipLeaveOnInt\":false,\"StatsiteAddr\":\"\",\"StatsdAddr\":\"\",\"Protocol\":2,\"EnableDebug\":false,\"VerifyIncoming\":false,\"VerifyOutgoing\":false,\"CAFile\":\"\",\"CertFile\":\"\",\"KeyFile\":\"\",\"ServerName\":\"\",\"StartJoin\":[],\"StartJoinWan\":[],\"RetryJoin\":[],\"RetryMaxAttempts\":0,\"RetryIntervalRaw\":\"\",\"RetryJoinWan\":[],\"RetryMaxAttemptsWan\":0,\"RetryIntervalWanRaw\":\"\",\"UiDir\":\"\",\"PidFile\":\"\",\"EnableSyslog\":false,\"SyslogFacility\":\"LOCAL0\",\"RejoinAfterLeave\":false,\"CheckUpdateInterval\":300000000000,\"ACLDatacenter\":\"\",\"ACLTTL\":30000000000,\"ACLTTLRaw\":\"\",\"ACLDefaultPolicy\":\"allow\",\"ACLDownPolicy\":\"extend-cache\",\"Watches\":null,\"DisableRemoteExec\":false,\"DisableUpdateCheck\":false,\"DisableAnonymousSignature\":false,\"HTTPAPIResponseHeaders\":null,\"AtlasInfrastructure\":\"\",\"AtlasJoin\":false,\"Revision\":\"0c7ca91c74587d0a378831f63e189ac6bf7bab3f+CHANGES\",\"Version\":\"0.5.0\",\"VersionPrerelease\":\"\",\"UnixSockets\":{\"Usr\":\"\",\"Grp\":\"\",\"Perms\":\"\"}},\"Member\":{\"Name\":\"receivehead-lm\",\"Addr\":\"192.168.0.4\",\"Port\":8301,\"Tags\":{\"bootstrap\":\"1\",\"build\":\"0.5.0:0c7ca91c\",\"dc\":\"dc1\",\"port\":\"8300\",\"role\":\"consul\",\"vsn\":\"2\",\"vsn_max\":\"2\",\"vsn_min\":\"1\"},\"Status\":1,\"ProtocolMin\":1,\"ProtocolMax\":2,\"ProtocolCur\":2,\"DelegateMin\":2,\"DelegateMax\":4,\"DelegateCur\":4}}";
+
+ private class ModifiableDispatcher extends Dispatcher {
+
+ @Override
+ public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
+
+ switch (request.getPath()) {
+ case "/v1/agent/self":
+ return new MockResponse().setResponseCode(200).setBody(PING_RESPONSE);
+ case "/v1/kv/?recurse=true":
+ return new MockResponse()
+ .setResponseCode(200)
+ .addHeader("Content-Type", "application/json; charset=utf-8")
+ .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA.toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"}]");
+ }
+ return new MockResponse().setResponseCode(404);
+ }
+ }
+
+ private MockWebServer server;
+ private ModifiableDispatcher dispatcher;
+
+ @Before
+ public void setUp() throws Exception {
+ dispatcher = new ModifiableDispatcher();
+ runMockServer();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ server.shutdown();
+ }
+
+ @Test
+ public void shouldReadConfigsFromConsulConfigurationSource() throws Exception {
+ ConfigurationSource source = new ConsulConfigurationSourceBuilder()
+ .withHost(server.getHostName())
+ .withPort(server.getPort())
+ .build();
+
+ ConfigurationProvider provider = new ConfigurationProviderBuilder()
+ .withConfigurationSource(source)
+ .withEnvironment(new ImmutableEnvironment("us-west-1"))
+ .build();
+
+ assertThat(provider.getProperty("featureA.toggle", String.class)).isEqualTo("disabled");
+ }
+
+
+ private void runMockServer() throws IOException {
+ server = new MockWebServer();
+ server.setDispatcher(dispatcher);
+ server.start(0);
+ }
+}
diff --git a/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java b/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java
index 1095037..7faaa96 100644
--- a/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java
+++ b/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
import org.cfg4j.source.ConfigurationSource;
import org.cfg4j.source.context.environment.DefaultEnvironment;
import org.cfg4j.source.context.environment.Environment;
@@ -99,7 +100,21 @@ public ConfigurationProviderBuilder withEnvironment(Environment environment) {
/**
* Enable metrics emission for {@link ConfigurationProvider}s built by this builder. All metrics will be registered
- * with {@code metricRegistry} and prefixed by {@code prefix}.
+ * with {@code metricRegistry} and prefixed by {@code prefix}. Provider built by this builder will emit the following metrics:
+ *
Provider-level metrics:
+ *
+ * - allConfigurationAsProperties
+ * - getProperty
+ * - getPropertyGeneric
+ * - bind
+ *
+ * Source-level metrics
+ *
+ * - source.getConfiguration
+ * - source.init
+ * - source.reload
+ *
+ * Each of those metrics is of {@link Timer} type (i.e. includes execution time percentiles, execution count, etc.)
*
* @param metricRegistry metric registry for registering metrics
* @param prefix prefix for metric names
@@ -127,6 +142,8 @@ public ConfigurationProvider build() {
configurationSource = new MeteredConfigurationSource(metricRegistry.get(), prefix, configurationSource);
}
+ configurationSource.init();
+
reloadStrategy.register(configurationSource);
SimpleConfigurationProvider configurationProvider = new SimpleConfigurationProvider(configurationSource, environment);
diff --git a/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java b/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java
new file mode 100644
index 0000000..d9465c7
--- /dev/null
+++ b/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl)
+ *
+ * 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 org.cfg4j.source.inmemory;
+
+import static java.util.Objects.requireNonNull;
+
+import org.cfg4j.source.ConfigurationSource;
+import org.cfg4j.source.context.environment.Environment;
+
+import java.util.Properties;
+
+/**
+ * Simple in-memory {@link ConfigurationSource}. A reference to the properties object is being kept allowing this source to sync up
+ * on {@link #reload()} call.
+ */
+public class InMemoryConfigurationSource implements ConfigurationSource {
+
+ private Properties properties;
+ private final Properties sourceProperties;
+
+ /**
+ * Create in-memory configuration source with given {@code properties}. A reference to the properties object is being kept allowing
+ * this source to sync up on {@link #reload()} call.
+ *
+ * @param properties properties to seed source.
+ */
+ public InMemoryConfigurationSource(Properties properties) {
+ this.sourceProperties = requireNonNull(properties);
+ this.properties = (Properties) properties.clone();
+ }
+
+ @Override
+ public Properties getConfiguration(Environment environment) {
+ return (Properties) properties.clone();
+ }
+
+ @Override
+ public void init() {
+ // NOP
+ }
+
+ @Override
+ public void reload() {
+ properties = (Properties) sourceProperties.clone();
+ }
+
+ @Override
+ public String toString() {
+ return "InMemoryConfigurationSource{" +
+ "properties=" + properties +
+ '}';
+ }
+}
diff --git a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java
similarity index 96%
rename from cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java
rename to cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java
index f62640d..8b15811 100644
--- a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java
+++ b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java
@@ -31,7 +31,7 @@
@RunWith(MockitoJUnitRunner.class)
-public class SimpleConfigurationProviderIntegrationTest {
+public class SimpleConfigurationProviderWithClasspathIntegrationTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
diff --git a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java
new file mode 100644
index 0000000..eca3de9
--- /dev/null
+++ b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl)
+ *
+ * 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 org.cfg4j.provider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.codahale.metrics.MetricRegistry;
+import org.cfg4j.source.ConfigurationSource;
+import org.cfg4j.source.inmemory.InMemoryConfigurationSource;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Properties;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class SimpleConfigurationProviderWithMeteredSourceIntegrationTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MetricRegistry metricRegistry = new MetricRegistry();
+
+ @Test
+ public void shouldEmitMetrics() throws Exception {
+ ConfigurationProvider provider = getConfigurationProvider();
+
+ provider.getProperty("some.setting", Boolean.class);
+
+ assertThat(metricRegistry.getTimers()).containsOnlyKeys(
+ "testService.allConfigurationAsProperties",
+ "testService.getProperty",
+ "testService.getPropertyGeneric",
+ "testService.bind",
+ "testService.source.getConfiguration",
+ "testService.source.init",
+ "testService.source.reload"
+ );
+ }
+
+ private ConfigurationProvider getConfigurationProvider() {
+ Properties properties = new Properties();
+ properties.put("some.setting", "true");
+
+ ConfigurationSource source = new InMemoryConfigurationSource(properties);
+
+ return new ConfigurationProviderBuilder()
+ .withConfigurationSource(source)
+ .withMetrics(metricRegistry, "testService.")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java b/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java
new file mode 100644
index 0000000..86d9e95
--- /dev/null
+++ b/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl)
+ *
+ * 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 org.cfg4j.source.inmemory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.assertj.core.data.MapEntry;
+import org.cfg4j.source.context.environment.DefaultEnvironment;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Properties;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class InMemoryConfigurationSourceTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private InMemoryConfigurationSource source;
+ private Properties properties;
+
+ @Before
+ public void setUp() throws Exception {
+ properties = new Properties();
+ properties.put("sample.setting", "value");
+
+ source = new InMemoryConfigurationSource(properties);
+ source.init();
+ }
+
+ @Test
+ public void shouldReturnSourceProperties() throws Exception {
+ assertThat(source.getConfiguration(new DefaultEnvironment())).isEqualTo(properties);
+ }
+
+ @Test
+ public void shouldNotReactToChangesToSourceProperties() throws Exception {
+ properties.put("other.setting", "hello");
+
+ assertThat(source.getConfiguration(new DefaultEnvironment())).doesNotContain(MapEntry.entry("other.setting", "hello"));
+ }
+
+ @Test
+ public void reloadShouldReactToChangesToSourceProperties() throws Exception {
+ properties.put("other.setting", "hello");
+ source.reload();
+
+ assertThat(source.getConfiguration(new DefaultEnvironment())).contains(MapEntry.entry("other.setting", "hello"));
+ }
+
+}
\ No newline at end of file
diff --git a/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java
new file mode 100644
index 0000000..d89ff01
--- /dev/null
+++ b/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl)
+ *
+ * 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 org.cfg4j.source.git;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.cfg4j.provider.ConfigurationProvider;
+import org.cfg4j.provider.ConfigurationProviderBuilder;
+import org.cfg4j.source.ConfigurationSource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.nio.file.Paths;
+
+public class SimpleConfigurationProviderIntegrationTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private TempConfigurationGitRepo remoteRepo;
+
+ @Before
+ public void setUp() throws Exception {
+ remoteRepo = new TempConfigurationGitRepo("org.cfg4j-test-repo.git");
+ remoteRepo.changeProperty(Paths.get("application.properties"), "some.setting", "masterValue");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ remoteRepo.remove();
+ }
+
+ @Test
+ public void shouldReadConfigsFromGitConfigurationSource() throws Exception {
+ ConfigurationSource source = new GitConfigurationSourceBuilder()
+ .withRepositoryURI(remoteRepo.dirPath.toString())
+ .build();
+
+ ConfigurationProvider provider = new ConfigurationProviderBuilder()
+ .withConfigurationSource(source)
+ .build();
+
+ assertThat(provider.getProperty("some.setting", String.class)).isEqualTo("masterValue");
+ }
+}