diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java index 55760409..5db43b35 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java @@ -32,6 +32,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; @@ -67,28 +68,30 @@ public class RefreshEndpoint extends AbstractEndpoint> { @ManagedOperation public synchronized String[] refresh() { - Map before = extract(context.getEnvironment() + Map before = extract(this.context.getEnvironment() .getPropertySources()); addConfigFilesToEnvironment(); Set keys = changes(before, - extract(context.getEnvironment().getPropertySources())).keySet(); - scope.refreshAll(); + extract(this.context.getEnvironment().getPropertySources())).keySet(); + this.scope.refreshAll(); if (keys.isEmpty()) { return new String[0]; } - context.publishEvent(new EnvironmentChangeEvent(keys)); + this.context.publishEvent(new EnvironmentChangeEvent(keys)); return keys.toArray(new String[keys.size()]); } private void addConfigFilesToEnvironment() { ConfigurableApplicationContext capture = null; try { + StandardEnvironment environment = copyEnvironment(this.context.getEnvironment()); capture = new SpringApplicationBuilder(Empty.class).showBanner(false) - .web(false).environment(context.getEnvironment()).run(); - MutablePropertySources target = context.getEnvironment().getPropertySources(); - for (PropertySource source : capture.getEnvironment().getPropertySources()) { + .web(false).environment(environment).run(); + MutablePropertySources target = this.context.getEnvironment() + .getPropertySources(); + for (PropertySource source : environment.getPropertySources()) { String name = source.getName(); - if (!standardSources.contains(name)) { + if (!this.standardSources.contains(name)) { if (target.contains(name)) { target.replace(name, source); } @@ -109,13 +112,31 @@ public class RefreshEndpoint extends AbstractEndpoint> { ApplicationContext parent = capture.getParent(); if (parent instanceof ConfigurableApplicationContext) { capture = (ConfigurableApplicationContext) parent; - } else { + } + else { capture = null; } } } } + // Don't use ConfigurableEnvironment.merge() in case there are clashes with property source names + private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) { + StandardEnvironment environment = new StandardEnvironment(); + MutablePropertySources capturedPropertySources = environment + .getPropertySources(); + for (PropertySource source : capturedPropertySources) { + capturedPropertySources.remove(source.getName()); + } + for (PropertySource source : input + .getPropertySources()) { + capturedPropertySources.addLast(source); + } + environment.setActiveProfiles(input.getActiveProfiles()); + environment.setDefaultProfiles(input.getDefaultProfiles()); + return environment; + } + @Override public Collection invoke() { return Arrays.asList(refresh()); @@ -153,7 +174,7 @@ public class RefreshEndpoint extends AbstractEndpoint> { private Map extract(MutablePropertySources propertySources) { Map result = new HashMap(); for (PropertySource parent : propertySources) { - if (!standardSources.contains(parent.getName())) { + if (!this.standardSources.contains(parent.getName())) { extract(parent, result); } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java index f7ec3697..1931dc0a 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java @@ -27,12 +27,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -45,44 +45,52 @@ public class ConfigurationPropertiesRebinderIntegrationTests { @Autowired private ConfigurationPropertiesRebinder rebinder; - + @Autowired private ConfigurableEnvironment environment; @Test @DirtiesContext public void testSimpleProperties() throws Exception { - assertEquals("Hello scope!", properties.getMessage()); + assertEquals("Hello scope!", this.properties.getMessage()); // Change the dynamic property source... - EnvironmentTestUtils.addEnvironment(environment, "message:Foo"); + EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo"); // ...but don't refresh, so the bean stays the same: - assertEquals("Hello scope!", properties.getMessage()); - assertEquals(1, properties.getCount()); + assertEquals("Hello scope!", this.properties.getMessage()); + assertEquals(1, this.properties.getCount()); } @Test @DirtiesContext public void testRefresh() throws Exception { - assertEquals(1, properties.getCount()); - assertEquals("Hello scope!", properties.getMessage()); + assertEquals(1, this.properties.getCount()); + assertEquals("Hello scope!", this.properties.getMessage()); // Change the dynamic property source... - EnvironmentTestUtils.addEnvironment(environment, "message:Foo"); + EnvironmentTestUtils.addEnvironment(this.environment, "message:Foo"); // ...and then refresh, so the bean is re-initialized: - rebinder.rebind(); - assertEquals("Foo", properties.getMessage()); - assertEquals(2, properties.getCount()); + this.rebinder.rebind(); + assertEquals("Foo", this.properties.getMessage()); + assertEquals(2, this.properties.getCount()); } - + @Configuration @EnableConfigurationProperties - @Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) + @Import({RefreshConfiguration.RebinderConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) protected static class TestConfiguration { - + @Bean protected TestProperties properties() { return new TestProperties(); } - + + } + + // Hack out a protected inner class for testing + protected static class RefreshConfiguration extends RefreshAutoConfiguration { + @Configuration + protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderConfiguration { + + } } @ConfigurationProperties @@ -91,16 +99,16 @@ public class ConfigurationPropertiesRebinderIntegrationTests { private int delay; private int count = 0; public int getCount() { - return count; + return this.count; } public String getMessage() { - return message; + return this.message; } public void setMessage(String message) { this.message = message; } public int getDelay() { - return delay; + return this.delay; } public void setDelay(int delay) { this.delay = delay; @@ -110,5 +118,5 @@ public class ConfigurationPropertiesRebinderIntegrationTests { this.count ++; } } - + } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java new file mode 100644 index 00000000..c5252316 --- /dev/null +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderListIntegrationTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * 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.springframework.cloud.context.properties; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderListIntegrationTests.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@SpringApplicationConfiguration(classes=TestConfiguration.class) +@RunWith(SpringJUnit4ClassRunner.class) +@IntegrationTest("messages=one,two") +public class ConfigurationPropertiesRebinderListIntegrationTests { + + @Autowired + private TestProperties properties; + + @Autowired + private ConfigurationPropertiesRebinder rebinder; + + @Autowired + private ConfigurableEnvironment environment; + + @Test + @DirtiesContext + public void testAppendProperties() throws Exception { + assertEquals("[one, two]", this.properties.getMessages().toString()); + EnvironmentTestUtils.addEnvironment(this.environment, "messages[0]:foo"); + this.rebinder.rebind(); + assertEquals("[foo, two]", this.properties.getMessages().toString()); + } + + @Test + @DirtiesContext + @Ignore("Can't rebind to list and re-initialize it (need refresh scope for this to work)") + public void testReplaceProperties() throws Exception { + assertEquals("[one, two]", this.properties.getMessages().toString()); + @SuppressWarnings("unchecked") + Map map = (Map) this.environment.getPropertySources().get("integrationTest").getSource(); + map.clear(); + EnvironmentTestUtils.addEnvironment(this.environment, "messages[0]:foo"); + this.rebinder.rebind(); + assertEquals("[foo]", this.properties.getMessages().toString()); + } + + @Test + @DirtiesContext + public void testReplacePropertiesWithCommaSeparated() throws Exception { + assertEquals("[one, two]", this.properties.getMessages().toString()); + @SuppressWarnings("unchecked") + Map map = (Map) this.environment.getPropertySources().get("integrationTest").getSource(); + map.clear(); + EnvironmentTestUtils.addEnvironment(this.environment, "messages:foo"); + this.rebinder.rebind(); + assertEquals("[foo]", this.properties.getMessages().toString()); + } + + + @Configuration + @EnableConfigurationProperties + @Import({RefreshConfiguration.RebinderConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) + protected static class TestConfiguration { + + @Bean + protected TestProperties properties() { + return new TestProperties(); + } + + } + + // Hack out a protected inner class for testing + protected static class RefreshConfiguration extends RefreshAutoConfiguration { + @Configuration + protected static class RebinderConfiguration extends ConfigurationPropertiesRebinderConfiguration { + + } + } + + @ConfigurationProperties + protected static class TestProperties { + private List messages; + private int count; + public List getMessages() { + return this.messages; + } + public void setMessages(List messages) { + this.messages = messages; + } + public int getCount() { + return this.count; + } + @PostConstruct + public void init() { + this.count ++; + } + } + +} diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java index ec321706..70ba6a19 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java @@ -62,10 +62,10 @@ public class RefreshEndpointIntegrationTests { public void webAccess() throws Exception { TestRestTemplate template = new TestRestTemplate(); template.exchange( - getUrlEncodedEntity("http://localhost:" + port + "/env", "message", + getUrlEncodedEntity("http://localhost:" + this.port + "/env", "message", "Hello Dave!"), String.class); - template.postForObject("http://localhost:" + port + "/refresh", "", String.class); - String message = template.getForObject("http://localhost:" + port + "/", + template.postForObject("http://localhost:" + this.port + "/refresh", "", String.class); + String message = template.getForObject("http://localhost:" + this.port + "/", String.class); assertEquals("Hello Dave!", message); } @@ -73,7 +73,7 @@ public class RefreshEndpointIntegrationTests { private RequestEntity getUrlEncodedEntity(String uri, String key, String value) throws URISyntaxException { MultiValueMap env = new LinkedMultiValueMap( - Collections.singletonMap("message", Arrays.asList("Hello Dave!"))); + Collections.singletonMap(key, Arrays.asList(value))); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); RequestEntity> entity = new RequestEntity>( @@ -84,7 +84,7 @@ public class RefreshEndpointIntegrationTests { @Configuration @EnableAutoConfiguration protected static class ClientApp { - + @Bean @RefreshScope public Controller controller() { @@ -105,7 +105,7 @@ public class RefreshEndpointIntegrationTests { @RequestMapping("/") public String hello() { - return message; + return this.message; } } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeListBindingIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeListBindingIntegrationTests.java index 70b4de1b..a1dc82bf 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeListBindingIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeListBindingIntegrationTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,7 +60,7 @@ public class RefreshScopeListBindingIntegrationTests { @Test @DirtiesContext - public void testSimpleProperties() throws Exception { + public void testAppendProperties() throws Exception { assertEquals("[one, two]", this.properties.getMessages().toString()); assertTrue(this.properties instanceof Advised); EnvironmentTestUtils.addEnvironment(this.environment, "messages[0]:foo"); @@ -67,6 +68,18 @@ public class RefreshScopeListBindingIntegrationTests { assertEquals("[foo, two]", this.properties.getMessages().toString()); } + @Test + @DirtiesContext + public void testReplaceProperties() throws Exception { + assertEquals("[one, two]", this.properties.getMessages().toString()); + assertTrue(this.properties instanceof Advised); + @SuppressWarnings("unchecked") + Map map = (Map) this.environment.getPropertySources().get("integrationTest").getSource(); + map.clear(); + EnvironmentTestUtils.addEnvironment(this.environment, "messages[0]:foo"); + this.scope.refreshAll(); + assertEquals("[foo]", this.properties.getMessages().toString()); + } @Configuration @EnableConfigurationProperties