Make /refresh POST only and extend to non-bootstrap properties

This commit is contained in:
Dave Syer
2014-06-27 12:04:06 +01:00
parent 9bf40dcc3e
commit 95b7db0ac9
6 changed files with 107 additions and 43 deletions

View File

@@ -45,6 +45,7 @@ import org.springframework.platform.context.properties.ConfigurationPropertiesRe
import org.springframework.platform.context.restart.RestartEndpoint;
import org.springframework.platform.context.restart.RestartMvcEndpoint;
import org.springframework.platform.context.scope.refresh.RefreshScope;
import org.springframework.platform.endpoint.GenericPostableMvcEndpoint;
@Configuration
@ConditionalOnClass(RefreshScope.class)
@@ -88,21 +89,22 @@ public class RefreshAutoConfiguration {
@ConditionalOnClass(IntegrationMBeanExporter.class)
protected static class RefreshEndpointWithIntegration {
@Autowired
@Autowired(required=false)
private IntegrationMBeanExporter exporter;
@Bean
@ConditionalOnMissingBean
public RestartEndpoint restartEndpoint() {
RestartEndpoint endpoint = new RestartEndpoint();
if (exporter!=null) {
if (exporter != null) {
endpoint.setIntegrationMBeanExporter(exporter);
}
return endpoint;
}
}
@ConditionalOnMissingClass(name="org.springframework.integration.monitor.IntegrationMBeanExporter")
@ConditionalOnMissingClass(name = "org.springframework.integration.monitor.IntegrationMBeanExporter")
protected static class RefreshEndpointWithoutIntegration {
@Bean
@@ -131,13 +133,16 @@ public class RefreshAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RefreshEndpoint refreshEndpoint(
ConfigurableApplicationContext context,
ConfigServiceBootstrapConfiguration bootstrap) {
RefreshEndpoint endpoint = new RefreshEndpoint(context, bootstrap);
public RefreshEndpoint refreshEndpoint(ConfigurableApplicationContext context) {
RefreshEndpoint endpoint = new RefreshEndpoint(context);
return endpoint;
}
@Bean
public MvcEndpoint refreshMvcEndpoint(RefreshEndpoint endpoint) {
return new GenericPostableMvcEndpoint(endpoint);
}
}
@Configuration

View File

@@ -20,23 +20,25 @@ import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
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.core.env.StandardEnvironment;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.platform.bootstrap.config.ConfigServiceBootstrapConfiguration;
import org.springframework.platform.context.environment.EnvironmentChangeEvent;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* @author Dave Syer
@@ -46,28 +48,51 @@ import org.springframework.util.ReflectionUtils;
@ManagedResource
public class RefreshEndpoint extends AbstractEndpoint<Collection<String>> {
private Set<String> standardSources = new HashSet<String>(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 ConfigServiceBootstrapConfiguration bootstrap = new ConfigServiceBootstrapConfiguration();
public RefreshEndpoint(ConfigurableApplicationContext context, ConfigServiceBootstrapConfiguration bootstrap) {
public RefreshEndpoint(ConfigurableApplicationContext context) {
super("refresh");
this.context = context;
this.bootstrap = bootstrap;
}
@ManagedOperation
public synchronized String[] refresh() {
Map<String, Object> before = extract(context.getEnvironment().getPropertySources());
bootstrap.initialize(context);
new ConfigFileApplicationListener().onApplicationEvent(new ApplicationEnvironmentPreparedEvent(null, null, context.getEnvironment()));
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(context.getEnvironment().getPropertySources())).keySet();
if (keys.isEmpty()) {
return new String[0];
}
context.publishEvent(new EnvironmentChangeEvent(keys));
return keys.toArray(new String[keys.size()]);
return keys.toArray(new String[keys.size()]);
}
private void addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = new SpringApplicationBuilder(Empty.class).showBanner(
false).web(false).environment(context.getEnvironment()).run();
MutablePropertySources target = context.getEnvironment().getPropertySources();
for (PropertySource<?> source : capture.getEnvironment().getPropertySources()) {
String name = source.getName();
if (!standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
} else {
if (target.contains("defaultProperties")) {
target.addBefore("defaultProperties", source);
} else {
target.addLast(source);
}
}
}
}
}
@Override
@@ -105,8 +130,11 @@ public class RefreshEndpoint extends AbstractEndpoint<Collection<String>> {
private Map<String, Object> extract(MutablePropertySources propertySources) {
Map<String, Object> result = new HashMap<String, Object>();
PropertySource<?> parent = propertySources.get("bootstrap");
extract(parent, result);
for (PropertySource<?> parent : propertySources) {
if (!standardSources.contains(parent.getName())) {
extract(parent, result);
}
}
return result;
}
@@ -131,4 +159,9 @@ public class RefreshEndpoint extends AbstractEndpoint<Collection<String>> {
}
}
@Configuration
protected static class Empty {
}
}

View File

@@ -18,11 +18,11 @@ package org.springframework.platform.context.restart;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.platform.endpoint.GenericPostableMvcEndpoint;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -66,25 +66,5 @@ public class RestartMvcEndpoint extends EndpointMvcAdapter {
return new GenericPostableMvcEndpoint(((RestartEndpoint)getDelegate()).getResumeEndpoint());
}
private static class GenericPostableMvcEndpoint extends EndpointMvcAdapter {
public GenericPostableMvcEndpoint(Endpoint<?> delegate) {
super(delegate);
}
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@Override
public Object invoke() {
if (!getDelegate().isEnabled()) {
return new ResponseEntity<Map<String, String>>(Collections.singletonMap(
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
}
return super.invoke();
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.endpoint;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
public class GenericPostableMvcEndpoint extends EndpointMvcAdapter {
public GenericPostableMvcEndpoint(Endpoint<?> delegate) {
super(delegate);
}
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
@Override
public Object invoke() {
if (!getDelegate().isEnabled()) {
return new ResponseEntity<Map<String, String>>(Collections.singletonMap(
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
}
return super.invoke();
}
}

View File

@@ -86,7 +86,7 @@ public class RefreshScopeConcurrencyTests {
}
}
});
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertTrue(latch.await(1500, TimeUnit.MILLISECONDS));
logger.info("Refreshing");
scope.refreshAll();
assertEquals("Foo", service.getMessage());

View File

@@ -1,5 +1,5 @@
info:
component: Config Sample
component: Config Samples
endpoints:
restart:
enabled: true