From b92016b260904af2499738fac253d35a473916e7 Mon Sep 17 00:00:00 2001 From: Venil Noronha Date: Mon, 21 Mar 2016 06:45:50 +0530 Subject: [PATCH] Moved RefreshEndpoint logic to ContextRefresher. Fixes gh-101 --- .../RefreshAutoConfiguration.java | 11 +- .../RefreshEndpointAutoConfiguration.java | 12 +- .../context/refresh/ContextRefresher.java | 206 ++++++++++++++++++ .../cloud/endpoint/RefreshEndpoint.java | 196 +---------------- .../cloud/endpoint/RefreshEndpointTests.java | 21 +- 5 files changed, 245 insertions(+), 201 deletions(-) create mode 100644 spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/ContextRefresher.java diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java index c0d01bb5..9b0c94f3 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java @@ -22,8 +22,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.cloud.context.environment.EnvironmentManager; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.logging.LoggingRebinder; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -33,7 +35,7 @@ import org.springframework.core.env.ConfigurableEnvironment; * the Environment (e.g. rebinding logger levels). * * @author Dave Syer - * + * @author Venil Noronha */ @Configuration @ConditionalOnClass(RefreshScope.class) @@ -58,4 +60,11 @@ public class RefreshAutoConfiguration { return new EnvironmentManager(environment); } + @Bean + @ConditionalOnMissingBean + public ContextRefresher contextRefresher(ConfigurableApplicationContext context, + RefreshScope scope) { + return new ContextRefresher(context, scope); + } + } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshEndpointAutoConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshEndpointAutoConfiguration.java index 2d113cc4..97311965 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshEndpointAutoConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshEndpointAutoConfiguration.java @@ -35,18 +35,23 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.restart.RestartEndpoint; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.endpoint.RefreshEndpoint; import org.springframework.cloud.endpoint.event.RefreshEventListener; import org.springframework.cloud.health.RefreshScopeHealthIndicator; import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.integration.monitor.IntegrationMBeanExporter; +/** + * @author Dave Syer + * @author Spencer Gibb + * @author Venil Noronha + */ @Configuration @ConditionalOnClass(Endpoint.class) @AutoConfigureAfter(EndpointAutoConfiguration.class) @@ -112,9 +117,8 @@ public class RefreshEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean - public RefreshEndpoint refreshEndpoint(ConfigurableApplicationContext context, - RefreshScope scope) { - RefreshEndpoint endpoint = new RefreshEndpoint(context, scope); + public RefreshEndpoint refreshEndpoint(ContextRefresher contextRefresher) { + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); return endpoint; } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/ContextRefresher.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/ContextRefresher.java new file mode 100644 index 00000000..7581de00 --- /dev/null +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/ContextRefresher.java @@ -0,0 +1,206 @@ +package org.springframework.cloud.context.refresh; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.boot.Banner.Mode; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.scope.refresh.RefreshScope; +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.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.web.context.support.StandardServletEnvironment; + +/** + * @author Dave Syer + * @author Venil Noronha + */ +public class ContextRefresher { + + private static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs"; + + private Set standardSources = new HashSet( + Arrays.asList(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, + StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME, + StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, + StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); + + private ConfigurableApplicationContext context; + private RefreshScope scope; + + public ContextRefresher(ConfigurableApplicationContext context, RefreshScope scope) { + this.context = context; + this.scope = scope; + } + + public synchronized Set refresh() { + Map before = extract( + this.context.getEnvironment().getPropertySources()); + addConfigFilesToEnvironment(); + Set keys = changes(before, + extract(this.context.getEnvironment().getPropertySources())).keySet(); + this.context.publishEvent(new EnvironmentChangeEvent(keys)); + this.scope.refreshAll(); + return keys; + } + + private void addConfigFilesToEnvironment() { + ConfigurableApplicationContext capture = null; + try { + StandardEnvironment environment = copyEnvironment( + this.context.getEnvironment()); + capture = new SpringApplicationBuilder(Empty.class).bannerMode(Mode.OFF) + .web(false).environment(environment).run(); + if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) { + environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE); + } + MutablePropertySources target = this.context.getEnvironment() + .getPropertySources(); + String targetName = null; + for (PropertySource source : environment.getPropertySources()) { + String name = source.getName(); + if (target.contains(name)) { + targetName = name; + } + if (!this.standardSources.contains(name)) { + if (target.contains(name)) { + target.replace(name, source); + } + else { + if (targetName != null) { + target.addAfter(targetName, source); + } + else { + if (target.contains("defaultProperties")) { + target.addBefore("defaultProperties", source); + } + else { + target.addLast(source); + } + } + } + } + } + } + finally { + ConfigurableApplicationContext closeable = capture; + while (closeable != null) { + closeable.close(); + ApplicationContext parent = closeable.getParent(); + if (parent instanceof ConfigurableApplicationContext) { + closeable = (ConfigurableApplicationContext) parent; + } + else { + closeable = 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()); + Map map = new HashMap(); + map.put("spring.jmx.enabled", false); + map.put("spring.main.sources", ""); + capturedPropertySources + .addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map)); + return environment; + } + + private Map changes(Map before, + Map after) { + Map result = new HashMap(); + for (String key : before.keySet()) { + if (!after.containsKey(key)) { + result.put(key, null); + } + else if (!equal(before.get(key), after.get(key))) { + result.put(key, after.get(key)); + } + } + for (String key : after.keySet()) { + if (!before.containsKey(key)) { + result.put(key, after.get(key)); + } + } + return result; + } + + private boolean equal(Object one, Object two) { + if (one == null && two == null) { + return true; + } + if (one == null || two == null) { + return false; + } + return one.equals(two); + } + + private Map extract(MutablePropertySources propertySources) { + Map result = new HashMap(); + List> sources = new ArrayList>(); + for (PropertySource source : propertySources) { + sources.add(0, source); + } + for (PropertySource source : sources) { + if (!this.standardSources.contains(source.getName())) { + extract(source, result); + } + } + return result; + } + + private void extract(PropertySource parent, Map result) { + if (parent instanceof CompositePropertySource) { + try { + List> sources = new ArrayList>(); + for (PropertySource source : ((CompositePropertySource) parent) + .getPropertySources()) { + sources.add(0, source); + } + for (PropertySource source : sources) { + extract(source, result); + } + } + catch (Exception e) { + return; + } + } + else if (parent instanceof EnumerablePropertySource) { + for (String key : ((EnumerablePropertySource) parent).getPropertyNames()) { + result.put(key, parent.getProperty(key)); + } + } + } + + @Configuration + protected static class Empty { + + } + +} diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/endpoint/RefreshEndpoint.java b/spring-cloud-context/src/main/java/org/springframework/cloud/endpoint/RefreshEndpoint.java index 2b5998e3..4efed776 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/endpoint/RefreshEndpoint.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/endpoint/RefreshEndpoint.java @@ -16,222 +16,40 @@ package org.springframework.cloud.endpoint; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; -import org.springframework.boot.Banner.Mode; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.context.environment.EnvironmentChangeEvent; -import org.springframework.cloud.context.scope.refresh.RefreshScope; -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.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.core.env.PropertySource; -import org.springframework.core.env.StandardEnvironment; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.web.context.support.StandardServletEnvironment; /** * @author Dave Syer - * + * @author Venil Noronha */ @ConfigurationProperties(prefix = "endpoints.refresh", ignoreUnknownFields = false) @ManagedResource public class RefreshEndpoint extends AbstractEndpoint> { - private static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs"; + private ContextRefresher contextRefresher; - private Set standardSources = new HashSet( - Arrays.asList(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, - StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, - StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME, - StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, - StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); - - private ConfigurableApplicationContext context; - - private RefreshScope scope; - - public RefreshEndpoint(ConfigurableApplicationContext context, RefreshScope scope) { + public RefreshEndpoint(ContextRefresher contextRefresher) { super("refresh"); - this.context = context; - this.scope = scope; + this.contextRefresher = contextRefresher; } @ManagedOperation - public synchronized String[] refresh() { - Map before = extract( - this.context.getEnvironment().getPropertySources()); - addConfigFilesToEnvironment(); - Set keys = changes(before, - extract(this.context.getEnvironment().getPropertySources())).keySet(); - this.context.publishEvent(new EnvironmentChangeEvent(keys)); - this.scope.refreshAll(); + public String[] refresh() { + Set keys = contextRefresher.refresh(); 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).bannerMode(Mode.OFF) - .web(false).environment(environment).run(); - if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) { - environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE); - } - MutablePropertySources target = this.context.getEnvironment() - .getPropertySources(); - String targetName = null; - for (PropertySource source : environment.getPropertySources()) { - String name = source.getName(); - if (target.contains(name)) { - targetName = name; - } - if (!this.standardSources.contains(name)) { - if (target.contains(name)) { - target.replace(name, source); - } - else { - if (targetName != null) { - target.addAfter(targetName, source); - } - else { - if (target.contains("defaultProperties")) { - target.addBefore("defaultProperties", source); - } - else { - target.addLast(source); - } - } - } - } - } - } - finally { - ConfigurableApplicationContext closeable = capture; - while (closeable != null) { - closeable.close(); - ApplicationContext parent = closeable.getParent(); - if (parent instanceof ConfigurableApplicationContext) { - closeable = (ConfigurableApplicationContext) parent; - } - else { - closeable = 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()); - Map map = new HashMap(); - map.put("spring.jmx.enabled", false); - map.put("spring.main.sources", ""); - capturedPropertySources - .addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map)); - return environment; - } - @Override public Collection invoke() { return Arrays.asList(refresh()); } - private Map changes(Map before, - Map after) { - Map result = new HashMap(); - for (String key : before.keySet()) { - if (!after.containsKey(key)) { - result.put(key, null); - } - else if (!equal(before.get(key), after.get(key))) { - result.put(key, after.get(key)); - } - } - for (String key : after.keySet()) { - if (!before.containsKey(key)) { - result.put(key, after.get(key)); - } - } - return result; - } - - private boolean equal(Object one, Object two) { - if (one == null && two == null) { - return true; - } - if (one == null || two == null) { - return false; - } - return one.equals(two); - } - - private Map extract(MutablePropertySources propertySources) { - Map result = new HashMap(); - List> sources = new ArrayList>(); - for (PropertySource source : propertySources) { - sources.add(0, source); - } - for (PropertySource source : sources) { - if (!this.standardSources.contains(source.getName())) { - extract(source, result); - } - } - return result; - } - - private void extract(PropertySource parent, Map result) { - if (parent instanceof CompositePropertySource) { - try { - List> sources = new ArrayList>(); - for (PropertySource source : ((CompositePropertySource) parent) - .getPropertySources()) { - sources.add(0, source); - } - for (PropertySource source : sources) { - extract(source, result); - } - } - catch (Exception e) { - return; - } - } - else if (parent instanceof EnumerablePropertySource) { - for (String key : ((EnumerablePropertySource) parent).getPropertyNames()) { - result.put(key, parent.getProperty(key)); - } - } - } - - @Configuration - protected static class Empty { - - } - } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java index 10658e02..4f4f49a8 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java @@ -30,6 +30,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; import org.springframework.context.ApplicationEvent; @@ -49,7 +50,7 @@ import static org.junit.Assert.assertTrue; /** * @author Dave Syer - * + * @author Venil Noronha */ public class RefreshEndpointTests { @@ -70,7 +71,8 @@ public class RefreshEndpointTests { RefreshScope scope = new RefreshScope(); scope.setApplicationContext(this.context); EnvironmentTestUtils.addEnvironment(this.context, "spring.profiles.active=local"); - RefreshEndpoint endpoint = new RefreshEndpoint(this.context, scope); + ContextRefresher contextRefresher = new ContextRefresher(this.context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); Collection keys = endpoint.invoke(); assertTrue("Wrong keys: " + keys, keys.contains("added")); } @@ -84,7 +86,8 @@ public class RefreshEndpointTests { scope.setApplicationContext(this.context); EnvironmentTestUtils.addEnvironment(this.context, "spring.profiles.active=override"); - RefreshEndpoint endpoint = new RefreshEndpoint(this.context, scope); + ContextRefresher contextRefresher = new ContextRefresher(this.context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); Collection keys = endpoint.invoke(); assertTrue("Wrong keys: " + keys, keys.contains("message")); } @@ -99,7 +102,8 @@ public class RefreshEndpointTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.cloud.bootstrap.sources=" + ExternalPropertySourceLocator.class.getName()); - RefreshEndpoint endpoint = new RefreshEndpoint(this.context, scope); + ContextRefresher contextRefresher = new ContextRefresher(this.context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); Collection keys = endpoint.invoke(); assertTrue("Wrong keys: " + keys, keys.contains("external.message")); } @@ -116,7 +120,8 @@ public class RefreshEndpointTests { // construct the environment for refresh) EnvironmentTestUtils.addEnvironment(this.context, "spring.main.sources=" + ExternalPropertySourceLocator.class.getName()); - RefreshEndpoint endpoint = new RefreshEndpoint(this.context, scope); + ContextRefresher contextRefresher = new ContextRefresher(this.context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); Collection keys = endpoint.invoke(); assertFalse("Wrong keys: " + keys, keys.contains("external.message")); } @@ -127,7 +132,8 @@ public class RefreshEndpointTests { .bannerMode(Mode.OFF).run(); RefreshScope scope = new RefreshScope(); scope.setApplicationContext(this.context); - RefreshEndpoint endpoint = new RefreshEndpoint(this.context, scope); + ContextRefresher contextRefresher = new ContextRefresher(this.context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); Empty empty = this.context.getBean(Empty.class); endpoint.invoke(); int after = empty.events.size(); @@ -141,7 +147,8 @@ public class RefreshEndpointTests { .web(false).bannerMode(Mode.OFF).run(); RefreshScope scope = new RefreshScope(); scope.setApplicationContext(context); - RefreshEndpoint endpoint = new RefreshEndpoint(context, scope); + ContextRefresher contextRefresher = new ContextRefresher(context, scope); + RefreshEndpoint endpoint = new RefreshEndpoint(contextRefresher); int count = countShutdownHooks(); endpoint.invoke(); int after = countShutdownHooks();