Add RefreshEndpoint

This commit is contained in:
Dave Syer
2014-06-16 14:16:00 +01:00
parent d8c51cec81
commit 04c2bba3db
21 changed files with 218 additions and 21 deletions

View File

@@ -27,9 +27,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
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.platform.bootstrap.config.ConfigServiceBootstrapConfiguration;
import org.springframework.platform.config.client.RefreshEndpoint;
import org.springframework.platform.context.environment.EnvironmentManager;
import org.springframework.platform.context.environment.EnvironmentManagerMvcEndpoint;
import org.springframework.platform.context.properties.ConfigurationPropertiesRebinder;
@@ -66,6 +69,14 @@ public class RefreshAutoConfiguration {
return rebinder;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnExpression("${endpoints.refresh.enabled:true}")
public RefreshEndpoint refreshEndpoint(ConfigurableApplicationContext context, ConfigServiceBootstrapConfiguration bootstrap) {
RefreshEndpoint endpoint = new RefreshEndpoint(context, bootstrap);
return endpoint;
}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(EnvironmentEndpoint.class)

View File

@@ -106,6 +106,7 @@ public class BootstrapApplicationListener implements
context.close();
}
});
application.addInitializers(new AncestorInitializer(context));
return context;
}
@@ -134,5 +135,23 @@ public class BootstrapApplicationListener implements
public int getOrder() {
return this.order;
}
private static class AncestorInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private ConfigurableApplicationContext parent;
public AncestorInitializer(ConfigurableApplicationContext parent) {
this.parent = parent;
}
@Override
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent()!=null) {
context = (ConfigurableApplicationContext) context.getParent();
}
context.setParent(parent);
}
}
}

View File

@@ -17,12 +17,12 @@
package org.springframework.platform.bootstrap.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
@@ -30,7 +30,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.platform.config.client.ConfigServicePropertySourceLocator;
import org.springframework.platform.config.client.PropertySourceLocator;
/**
* @author Dave Syer
@@ -45,6 +48,11 @@ public class ConfigServiceBootstrapConfiguration implements
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<PropertySourceLocator>();
public void setPropertySourceLocators(
Collection<PropertySourceLocator> propertySourceLocators) {
this.propertySourceLocators = new ArrayList<PropertySourceLocator>(propertySourceLocators);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
@@ -66,12 +74,16 @@ public class ConfigServiceBootstrapConfiguration implements
empty = false;
}
if (!empty) {
applicationContext.getEnvironment().getPropertySources().addFirst(composite);
MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
if (propertySources.contains("bootstrap")) {
propertySources.replace("bootstrap", composite);
} else {
propertySources.addFirst(composite);
}
}
}
@Bean
@ConfigurationProperties("spring.platform.config")
public ConfigServicePropertySourceLocator configServicePropertySource() {
return new ConfigServicePropertySourceLocator();
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
package org.springframework.platform.config;
import java.util.ArrayList;
import java.util.List;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
package org.springframework.platform.config;
import java.util.Map;

View File

@@ -14,21 +14,25 @@
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
package org.springframework.platform.config.client;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.platform.config.Environment;
import org.springframework.platform.config.PropertySource;
import org.springframework.web.client.RestTemplate;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("spring.platform.config")
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
private String env = "development";

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.platform.bootstrap.config;
package org.springframework.platform.config.client;
import org.springframework.core.env.PropertySource;

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2013-2014 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.platform.config.client;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.platform.bootstrap.config.ConfigServiceBootstrapConfiguration;
import org.springframework.platform.context.environment.EnvironmentChangeEvent;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties(prefix = "endpoints.refresh", ignoreUnknownFields = false)
public class RefreshEndpoint extends AbstractEndpoint<Collection<String>> {
private ConfigurableApplicationContext context;
private ConfigServiceBootstrapConfiguration bootstrap = new ConfigServiceBootstrapConfiguration();
public RefreshEndpoint(ConfigurableApplicationContext context, ConfigServiceBootstrapConfiguration bootstrap) {
super("refresh");
this.context = context;
this.bootstrap = bootstrap;
}
@Override
public Collection<String> invoke() {
Map<String, Object> before = extract(context.getEnvironment().getPropertySources());
bootstrap.initialize(context);
Set<String> keys = changes(before,
extract(context.getEnvironment().getPropertySources())).keySet();
if (keys.isEmpty()) {
return keys;
}
context.publishEvent(new EnvironmentChangeEvent(keys));
return keys;
}
private Map<String, Object> changes(Map<String, Object> before,
Map<String, Object> after) {
Map<String, Object> result = new HashMap<String, Object>();
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<String, Object> extract(MutablePropertySources propertySources) {
Map<String, Object> result = new HashMap<String, Object>();
PropertySource<?> parent = propertySources.get("bootstrap");
extract(parent, result);
return result;
}
private void extract(PropertySource<?> parent, Map<String, Object> result) {
if (parent instanceof CompositePropertySource) {
try {
Field field = ReflectionUtils.findField(CompositePropertySource.class,
"propertySources");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Set<PropertySource<?>> sources = (Set<PropertySource<?>>) field.get(parent);
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));
}
}
}
}

View File

@@ -31,7 +31,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.platform.bootstrap.config.PropertySourceLocator;
import org.springframework.platform.config.client.PropertySourceLocator;
/**
* @author Dave Syer

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.boot.env.PropertySourcesLoader" level="TRACE"/>
<!-- logger name="org.springframework.security" level="DEBUG"/-->
</configuration>

View File

@@ -21,14 +21,14 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
public class ApplicationTests {
private static int configPort;
private static int configPort = 0;
@Value("${local.server.port}")
private int port;
@BeforeClass
public static void startConfigServer() {
ConfigurableApplicationContext context = SpringApplication.run(org.springframework.platform.config.server.Application.class, "--server.port=0");
ConfigurableApplicationContext context = SpringApplication.run(org.springframework.platform.config.server.Application.class, "--server.port=" + configPort, "--spring.config.name=server");
configPort = ((EmbeddedWebApplicationContext)context).getEmbeddedServletContainer().getPort();
System.setProperty("config.port", "" + configPort);
}
@@ -38,5 +38,17 @@ public class ApplicationTests {
String foo = new TestRestTemplate().getForObject("http://localhost:" + port + "/env/foo", String.class);
assertEquals("bar", foo);
}
public static void main(String[] args) {
System.setProperty("spring.jmx.default_domain", "platform.config.server");
System.setProperty("endpoints.jmx.domain", "platform.config.server");
System.setProperty("spring.platform.config.server.basedir", "target/config");
configPort = 8888;
startConfigServer();
System.clearProperty("spring.jmx.default_domain");
System.clearProperty("endpoints.jmx.domain");
System.clearProperty("spring.platform.config.server.basedir");
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,9 @@
info:
component: Config Server
spring:
application:
name: configserver
server:
port: 8888
management:
contextPath: /admin

View File

@@ -10,7 +10,7 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.platform.config.server;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
/**

View File

@@ -24,8 +24,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.util.FileUtils;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.bootstrap.config.PropertySource;
import org.springframework.platform.config.Environment;
import org.springframework.platform.config.PropertySource;
import org.springframework.util.Assert;
/**

View File

@@ -24,8 +24,8 @@ import java.util.Set;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.bootstrap.config.PropertySource;
import org.springframework.platform.config.Environment;
import org.springframework.platform.config.PropertySource;
import org.springframework.web.context.support.StandardServletEnvironment;
/**

View File

@@ -22,7 +22,7 @@ import java.util.List;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
import org.springframework.util.StringUtils;
/**

View File

@@ -4,4 +4,6 @@ spring:
application:
name: configserver
server:
port: 8888
port: 8888
management:
context_path: /admin

View File

@@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

View File

@@ -24,7 +24,7 @@ import java.io.File;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
/**
* @author Dave Syer

View File

@@ -18,7 +18,7 @@ package org.springframework.platform.config.server;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.platform.bootstrap.config.Environment;
import org.springframework.platform.config.Environment;
/**