diff --git a/pom.xml b/pom.xml
index 54949350..93e15e7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,25 @@
spring-boot-starter-webtrue
+
+ org.springframework.boot
+ spring-boot-starter-aop
+ true
+
+
+ org.springframework.security
+ spring-security-crypto
+
+
+ org.springframework.security
+ spring-security-rsa
+ true
+
+
+ org.springframework.integration
+ spring-integration-jmx
+ true
+ org.projectlomboklombok
diff --git a/src/main/java/org/springframework/cloud/autoconfigure/LifecycleMvcEndpointAutoConfiguration.java b/src/main/java/org/springframework/cloud/autoconfigure/LifecycleMvcEndpointAutoConfiguration.java
new file mode 100644
index 00000000..8032236f
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/autoconfigure/LifecycleMvcEndpointAutoConfiguration.java
@@ -0,0 +1,85 @@
+/*
+ * 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.cloud.autoconfigure;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
+import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.cloud.bootstrap.config.RefreshEndpoint;
+import org.springframework.cloud.context.environment.EnvironmentManager;
+import org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint;
+import org.springframework.cloud.context.restart.RestartEndpoint;
+import org.springframework.cloud.context.restart.RestartMvcEndpoint;
+import org.springframework.cloud.endpoint.GenericPostableMvcEndpoint;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Autoconfiguration for some MVC endpoints governing the application context lifecycle.
+ * Provides restart, pause, resume, refresh (environment) and environment update
+ * endpoints.
+ *
+ * @author Dave Syer
+ *
+ */
+@Configuration
+@ConditionalOnClass(EnvironmentEndpoint.class)
+@ConditionalOnProperty(value = "endpoints.env.enabled", matchIfMissing = true)
+@ConditionalOnWebApplication
+@ConditionalOnBean(RestartEndpoint.class)
+@AutoConfigureAfter({ WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class,
+ RefreshAutoConfiguration.class })
+public class LifecycleMvcEndpointAutoConfiguration {
+
+ @Autowired
+ private RestartEndpoint restartEndpoint;
+
+ @Bean
+ @ConditionalOnBean(EnvironmentEndpoint.class)
+ public EnvironmentManagerMvcEndpoint environmentManagerEndpoint(
+ EnvironmentEndpoint delegate, EnvironmentManager environment) {
+ return new EnvironmentManagerMvcEndpoint(delegate, environment);
+ }
+
+ @Bean
+ @ConditionalOnBean(RefreshEndpoint.class)
+ public MvcEndpoint refreshMvcEndpoint(RefreshEndpoint endpoint) {
+ return new GenericPostableMvcEndpoint(endpoint);
+ }
+
+ @Bean
+ public RestartMvcEndpoint restartMvcEndpoint() {
+ return new RestartMvcEndpoint(restartEndpoint);
+ }
+
+ @Bean
+ public MvcEndpoint pauseMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
+ return restartEndpoint.getPauseEndpoint();
+ }
+
+ @Bean
+ public MvcEndpoint resumeMvcEndpoint(RestartMvcEndpoint restartEndpoint) {
+ return restartEndpoint.getResumeEndpoint();
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java b/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java
new file mode 100644
index 00000000..c0222be5
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java
@@ -0,0 +1,216 @@
+/*
+ * 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.cloud.autoconfigure;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
+import org.springframework.boot.actuate.endpoint.Endpoint;
+import org.springframework.boot.actuate.endpoint.InfoEndpoint;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
+import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar;
+import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration;
+import org.springframework.cloud.bootstrap.config.RefreshEndpoint;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
+import org.springframework.cloud.context.environment.EnvironmentManager;
+import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
+import org.springframework.cloud.context.restart.RestartEndpoint;
+import org.springframework.cloud.context.scope.refresh.RefreshScope;
+import org.springframework.cloud.logging.LoggingRebinder;
+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;
+
+/**
+ * Autoconfiguration for the refresh scope and associated features to do with changes in
+ * the Environment (e.g. rebinding logger levels).
+ *
+ * @author Dave Syer
+ *
+ */
+@Configuration
+@ConditionalOnClass(RefreshScope.class)
+@AutoConfigureAfter(WebMvcAutoConfiguration.class)
+public class RefreshAutoConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public static RefreshScope refreshScope() {
+ return new RefreshScope();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public static LoggingRebinder loggingRebinder() {
+ return new LoggingRebinder();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public EnvironmentManager environmentManager(ConfigurableEnvironment environment) {
+ return new EnvironmentManager(environment);
+ }
+
+ @Configuration
+ @ConditionalOnClass(InfoEndpoint.class)
+ @ConditionalOnBean(EndpointAutoConfiguration.class)
+ protected static class InfoEndpointRebinderConfiguration implements
+ ApplicationListener {
+
+ @Autowired
+ private EndpointAutoConfiguration endpoints;
+
+ @Autowired
+ private ConfigurableEnvironment environment;
+
+ private Map map = new LinkedHashMap();
+
+ @Override
+ public void onApplicationEvent(EnvironmentChangeEvent event) {
+ for (String key : event.getKeys()) {
+ if (key.startsWith("info.")) {
+ this.map.put(key.substring("info.".length()),
+ this.environment.getProperty(key));
+ }
+ }
+ }
+
+ @Bean
+ public InfoEndpoint infoEndpoint() throws Exception {
+ return new InfoEndpoint(this.endpoints.infoEndpoint().invoke()) {
+ @Override
+ public Map invoke() {
+ Map info = new LinkedHashMap(
+ super.invoke());
+ info.putAll(InfoEndpointRebinderConfiguration.this.map);
+ return info;
+ }
+ };
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
+ protected static class ConfigurationPropertiesRebinderConfiguration implements
+ BeanFactoryAware {
+
+ private BeanFactory context;
+
+ @Override
+ public void setBeanFactory(BeanFactory applicationContext) throws BeansException {
+ this.context = applicationContext;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ConfigurationPropertiesRebinder configurationPropertiesRebinder() {
+ // Since this is a BeanPostProcessor we have to be super careful not to cause
+ // a cascade of bean instantiation. Knowing the *name* of the beans we need is
+ // super optimal, but a little brittle (unfortunately we have no choice).
+ ConfigurationPropertiesBindingPostProcessor binder = this.context
+ .getBean(
+ ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME,
+ ConfigurationPropertiesBindingPostProcessor.class);
+ ConfigurationBeanFactoryMetaData metaData = this.context.getBean(
+ ConfigurationPropertiesBindingPostProcessorRegistrar.BINDER_BEAN_NAME
+ + ".store", ConfigurationBeanFactoryMetaData.class);
+ ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
+ binder);
+ rebinder.setBeanMetaDataStore(metaData);
+ return rebinder;
+ }
+ }
+
+ @ConditionalOnClass(Endpoint.class)
+ protected static class RefreshEndpointsConfiguration {
+
+ @ConditionalOnClass(IntegrationMBeanExporter.class)
+ protected static class RestartEndpointWithIntegration {
+
+ @Autowired(required = false)
+ private IntegrationMBeanExporter exporter;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RestartEndpoint restartEndpoint() {
+ RestartEndpoint endpoint = new RestartEndpoint();
+ if (this.exporter != null) {
+ endpoint.setIntegrationMBeanExporter(this.exporter);
+ }
+ return endpoint;
+ }
+
+ }
+
+ @ConditionalOnMissingClass(name = "org.springframework.integration.monitor.IntegrationMBeanExporter")
+ protected static class RestartEndpointWithoutIntegration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RestartEndpoint restartEndpoint() {
+ return new RestartEndpoint();
+ }
+ }
+
+ @Bean
+ @ConfigurationProperties("endpoints.pause")
+ public Endpoint pauseEndpoint(RestartEndpoint restartEndpoint) {
+ return restartEndpoint.getPauseEndpoint();
+ }
+
+ @Bean
+ @ConfigurationProperties("endpoints.resume")
+ public Endpoint resumeEndpoint(RestartEndpoint restartEndpoint) {
+ return restartEndpoint.getResumeEndpoint();
+ }
+
+ @Configuration
+ @ConditionalOnProperty(value = "endpoints.refresh.enabled", matchIfMissing = true)
+ @ConditionalOnBean(PropertySourceBootstrapConfiguration.class)
+ protected static class RefreshEndpointConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RefreshEndpoint refreshEndpoint(
+ ConfigurableApplicationContext context, RefreshScope scope) {
+ RefreshEndpoint endpoint = new RefreshEndpoint(context, scope);
+ return endpoint;
+ }
+
+ }
+
+ }
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java
new file mode 100644
index 00000000..39e0d553
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java
@@ -0,0 +1,217 @@
+/*
+ * 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.cloud.bootstrap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.env.ConfigurableEnvironment;
+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.core.io.support.SpringFactoriesLoader;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A listener that prepares a SpringApplication (e.g. populating its Environment) by
+ * delegating to {@link ApplicationContextInitializer} beans in a separate bootstrap
+ * context. The bootstrap context is a SpringApplication created from sources defined in
+ * spring.factories as {@link BootstrapConfiguration}, and initialized with external
+ * config taken from "bootstrap.properties" (or yml), instead of the normal
+ * "application.properties".
+ *
+ * @author Dave Syer
+ *
+ */
+public class BootstrapApplicationListener implements
+ ApplicationListener, Ordered {
+
+ public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";
+
+ public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
+
+ private int order = DEFAULT_ORDER;
+
+ @Override
+ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+ ConfigurableEnvironment environment = event.getEnvironment();
+ if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
+ true)) {
+ return;
+ }
+ // don't listen to events in a bootstrap context
+ if (environment.getPropertySources().contains("bootstrapInProgress")) {
+ return;
+ }
+ ConfigurableApplicationContext context = bootstrapServiceContext(environment,
+ event.getSpringApplication());
+ apply(context, event.getSpringApplication(), environment);
+ }
+
+ private ConfigurableApplicationContext bootstrapServiceContext(
+ ConfigurableEnvironment environment, final SpringApplication application) {
+ StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
+ MutablePropertySources bootstrapProperties = bootstrapEnvironment
+ .getPropertySources();
+ for (PropertySource> source : bootstrapProperties) {
+ bootstrapProperties.remove(source.getName());
+ }
+ String configName = environment
+ .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
+ String configLocation = environment
+ .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
+ Map bootstrapMap = new HashMap<>();
+ bootstrapMap.put("spring.config.name", configName);
+ if (StringUtils.hasText(configLocation)) {
+ bootstrapMap.put("spring.config.location", configLocation);
+ }
+ bootstrapProperties.addFirst(new MapPropertySource(
+ BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
+ bootstrapProperties.addFirst(new MapPropertySource("bootstrapInProgress",
+ Collections. emptyMap()));
+ for (PropertySource> source : environment.getPropertySources()) {
+ bootstrapProperties.addLast(source);
+ }
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ // Use names and ensure unique to protect against duplicates
+ List names = SpringFactoriesLoader.loadFactoryNames(
+ BootstrapConfiguration.class, classLoader);
+ // TODO: is it possible or sensible to share a ResourceLoader?
+ SpringApplicationBuilder builder = new SpringApplicationBuilder()
+ .profiles(environment.getActiveProfiles()).showBanner(false)
+ .environment(bootstrapEnvironment)
+ .properties("spring.application.name:" + configName).web(false);
+ List> sources = new ArrayList<>();
+ for (String name : names) {
+ Class> cls = ClassUtils.resolveClassName(name, null);
+ try {
+ cls.getDeclaredAnnotations();
+ }
+ catch (Exception e) {
+ continue;
+ }
+ sources.add(cls);
+ }
+ builder.sources(sources.toArray(new Class[sources.size()]));
+ final ConfigurableApplicationContext context = builder.run();
+ // Make the bootstrap context a parent of the app context
+ addAncestorInitializer(application, context);
+ bootstrapProperties.remove("bootstrapInProgress");
+ return context;
+ }
+
+ private void addAncestorInitializer(SpringApplication application,
+ ConfigurableApplicationContext context) {
+ boolean installed = false;
+ for (ApplicationContextInitializer> initializer : application.getInitializers()) {
+ if (initializer instanceof AncestorInitializer) {
+ installed = true;
+ // New parent
+ ((AncestorInitializer) initializer).setParent(context);
+ }
+ }
+ if (!installed) {
+ application.addInitializers(new AncestorInitializer(context));
+ }
+
+ }
+
+ private void apply(ConfigurableApplicationContext context,
+ SpringApplication application, ConfigurableEnvironment environment) {
+ @SuppressWarnings("rawtypes")
+ List initializers = getOrderedBeansOfType(context,
+ ApplicationContextInitializer.class);
+ application.addInitializers(initializers
+ .toArray(new ApplicationContextInitializer[initializers.size()]));
+ }
+
+ private List getOrderedBeansOfType(ListableBeanFactory context, Class type) {
+ List result = new ArrayList();
+ for (String name : context.getBeanNamesForType(type)) {
+ result.add(context.getBean(name, type));
+ }
+ AnnotationAwareOrderComparator.sort(result);
+ return result;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+ private static class AncestorInitializer implements
+ ApplicationContextInitializer, Ordered {
+
+ private ConfigurableApplicationContext parent;
+
+ public AncestorInitializer(ConfigurableApplicationContext parent) {
+ this.parent = parent;
+ }
+
+ public void setParent(ConfigurableApplicationContext parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public int getOrder() {
+ // Need to run not too late (so not unordered), so that, for instance, the
+ // ContextIdApplicationContextInitializer runs later and picks up the merged
+ // Environment. Also not too early so that other initializers can pick up the
+ // parent (especially the Environment).
+ return Ordered.HIGHEST_PRECEDENCE + 10;
+ }
+
+ @Override
+ public void initialize(ConfigurableApplicationContext context) {
+ preemptMerge(
+ context.getEnvironment().getPropertySources(),
+ new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, Collections
+ . emptyMap()));
+ while (context.getParent() != null && context.getParent() != context) {
+ context = (ConfigurableApplicationContext) context.getParent();
+ }
+ new ParentContextApplicationContextInitializer(parent).initialize(context);
+ }
+
+ private void preemptMerge(MutablePropertySources propertySources,
+ PropertySource> propertySource) {
+ if (propertySource != null
+ && !propertySources.contains(propertySource.getName())) {
+ propertySources.addFirst(propertySource);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfiguration.java b/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfiguration.java
new file mode 100644
index 00000000..d8df65d1
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfiguration.java
@@ -0,0 +1,38 @@
+/*
+ * 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.cloud.bootstrap;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A marker interface used as a key in META-INF/spring.factories. Entries in
+ * the factories file are used to create the bootstrap application context.
+ *
+ * @author Dave Syer
+ *
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface BootstrapConfiguration {
+
+ /**
+ * Exclude specific auto-configuration classes such that they will never be applied.
+ */
+ Class>[] exclude() default {};
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java
new file mode 100644
index 00000000..938b6b06
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java
@@ -0,0 +1,127 @@
+/*
+ * 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.cloud.bootstrap.config;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+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.bind.PropertySourcesPropertyValues;
+import org.springframework.boot.bind.RelaxedDataBinder;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.bootstrap.BootstrapApplicationListener;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
+import org.springframework.cloud.logging.LoggingRebinder;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.env.StandardEnvironment;
+
+/**
+ * @author Dave Syer
+ *
+ */
+@Configuration
+@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
+public class PropertySourceBootstrapConfiguration implements
+ ApplicationContextInitializer {
+
+ private static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME;
+
+ private static Log logger = LogFactory
+ .getLog(PropertySourceBootstrapConfiguration.class);
+
+ @Autowired(required = false)
+ private List propertySourceLocators = new ArrayList<>();
+
+ @Autowired
+ private PropertySourceBootstrapProperties properties;
+
+ public void setPropertySourceLocators(
+ Collection propertySourceLocators) {
+ this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
+ }
+
+ @Override
+ public void initialize(ConfigurableApplicationContext applicationContext) {
+ CompositePropertySource composite = new CompositePropertySource(
+ BOOTSTRAP_PROPERTY_SOURCE_NAME);
+ AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
+ boolean empty = true;
+ for (PropertySourceLocator locator : this.propertySourceLocators) {
+ PropertySource> source = null;
+ source = locator.locate(applicationContext.getEnvironment());
+ if (source == null) {
+ continue;
+ }
+ logger.info("Located property source: " + source);
+ composite.addPropertySource(source);
+ empty = false;
+ }
+ if (!empty) {
+ MutablePropertySources propertySources = applicationContext.getEnvironment()
+ .getPropertySources();
+ if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
+ propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
+ }
+ insertPropertySources(propertySources, composite);
+ setLogLevels(applicationContext.getEnvironment());
+ }
+ }
+
+ private void setLogLevels(ConfigurableEnvironment environment) {
+ LoggingRebinder rebinder = new LoggingRebinder();
+ rebinder.setEnvironment(environment);
+ // We can't fire the event in the ApplicationContext here (too early), but we can
+ // create our own listener and poke it (it doesn't need the key changes)
+ rebinder.onApplicationEvent(new EnvironmentChangeEvent(Collections
+ . emptySet()));
+ }
+
+ private void insertPropertySources(MutablePropertySources propertySources,
+ CompositePropertySource composite) {
+ MutablePropertySources incoming = new MutablePropertySources();
+ incoming.addFirst(composite);
+ PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
+ new RelaxedDataBinder(remoteProperties, "spring.cloud.config")
+ .bind(new PropertySourcesPropertyValues(incoming));
+ if (!remoteProperties.isAllowOverride()
+ || remoteProperties.isOverrideSystemProperties()) {
+ propertySources.addFirst(composite);
+ return;
+ }
+ if (propertySources
+ .contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
+ propertySources.addAfter(
+ StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
+ composite);
+ }
+ else {
+ propertySources.addLast(composite);
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java
new file mode 100644
index 00000000..cfe2692b
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java
@@ -0,0 +1,37 @@
+package org.springframework.cloud.bootstrap.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("spring.cloud.config")
+public class PropertySourceBootstrapProperties {
+
+ /**
+ * Flag to indicate that the external properties should override system properties.
+ * Default true.
+ */
+ private boolean overrideSystemProperties = true;
+
+ /**
+ * Flag to indicate that {@link #isSystemPropertiesOverride()
+ * systemPropertiesOverride} can be used. Set to false to prevent users from changing
+ * the default accidentally. Default true.
+ */
+ private boolean allowOverride = true;
+
+ public boolean isOverrideSystemProperties() {
+ return overrideSystemProperties;
+ }
+
+ public void setOverrideSystemProperties(boolean overrideSystemProperties) {
+ this.overrideSystemProperties = overrideSystemProperties;
+ }
+
+ public boolean isAllowOverride() {
+ return allowOverride;
+ }
+
+ public void setAllowOverride(boolean allowOverride) {
+ this.allowOverride = allowOverride;
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceLocator.java b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceLocator.java
new file mode 100644
index 00000000..96fce80d
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceLocator.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cloud.bootstrap.config;
+
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Strategy for locating (possibly remote) property sources for the Environment.
+ * Implementations should not fail unless they intend to prevent the application from
+ * starting.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface PropertySourceLocator {
+
+ /**
+ * @param environment the current Environment
+ * @return a PropertySource or null if there is none
+ *
+ * @throws IllegalStateException if there is a fail fast condition
+ */
+ PropertySource> locate(Environment environment);
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java b/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java
new file mode 100644
index 00000000..8a137b4f
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java
@@ -0,0 +1,165 @@
+/*
+ * 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.cloud.bootstrap.config;
+
+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.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.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.web.context.support.StandardServletEnvironment;
+
+/**
+ * @author Dave Syer
+ *
+ */
+@ConfigurationProperties(prefix = "endpoints.refresh", ignoreUnknownFields = false)
+@ManagedResource
+public class RefreshEndpoint extends AbstractEndpoint> {
+
+ 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) {
+ super("refresh");
+ this.context = context;
+ this.scope = scope;
+ }
+
+ @ManagedOperation
+ public synchronized String[] refresh() {
+ Map before = extract(context.getEnvironment().getPropertySources());
+ addConfigFilesToEnvironment();
+ Set keys = changes(before,
+ extract(context.getEnvironment().getPropertySources())).keySet();
+ scope.refreshAll();
+ if (keys.isEmpty()) {
+ return new String[0];
+ }
+ context.publishEvent(new EnvironmentChangeEvent(keys));
+ 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
+ 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();
+ for (PropertySource> parent : propertySources) {
+ if (!standardSources.contains(parent.getName())) {
+ extract(parent, result);
+ }
+ }
+ return result;
+ }
+
+ private void extract(PropertySource> parent, Map result) {
+ if (parent instanceof CompositePropertySource) {
+ try {
+ for (PropertySource> source : ((CompositePropertySource) parent).getPropertySources()) {
+ 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/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java b/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java
new file mode 100644
index 00000000..9bfa1feb
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java
@@ -0,0 +1,155 @@
+/*
+ * 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.cloud.bootstrap.encrypt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.bootstrap.encrypt.KeyProperties.KeyStore;
+import org.springframework.cloud.context.encrypt.EncryptorFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
+import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Dave Syer
+ *
+ */
+@Configuration
+@ConditionalOnClass({ TextEncryptor.class, RsaSecretEncryptor.class })
+@EnableConfigurationProperties(KeyProperties.class)
+public class EncryptionBootstrapConfiguration {
+
+ @Autowired(required = false)
+ private TextEncryptor encryptor;
+
+ @Autowired
+ private KeyProperties key;
+
+ @Configuration
+ @Conditional(KeyCondition.class)
+ @ConditionalOnClass(RsaSecretEncryptor.class)
+ protected static class RsaEncryptionConfiguration {
+
+ @Autowired
+ private KeyProperties key;
+
+ @Bean
+ @ConditionalOnMissingBean(TextEncryptor.class)
+ public TextEncryptor textEncryptor() {
+ KeyStore keyStore = key.getKeyStore();
+ if (keyStore.getLocation() != null && keyStore.getLocation().exists()) {
+ return new RsaSecretEncryptor(
+ new KeyStoreKeyFactory(keyStore.getLocation(), keyStore
+ .getPassword().toCharArray()).getKeyPair(
+ keyStore.getAlias(), keyStore.getSecret().toCharArray()));
+ }
+ return new EncryptorFactory().create(key.getKey());
+ }
+
+ }
+
+ @Configuration
+ @Conditional(KeyCondition.class)
+ @ConditionalOnMissingClass(name = "org.springframework.security.rsa.crypto.RsaSecretEncryptor")
+ protected static class VanillaEncryptionConfiguration {
+
+ @Autowired
+ private KeyProperties key;
+
+ @Bean
+ @ConditionalOnMissingBean(TextEncryptor.class)
+ public TextEncryptor textEncryptor() {
+ return new EncryptorFactory().create(key.getKey());
+ }
+
+ }
+
+ @Bean
+ public EnvironmentDecryptApplicationInitializer environmentDecryptApplicationListener() {
+ if (encryptor == null) {
+ encryptor = new FailsafeTextEncryptor();
+ }
+ EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(
+ encryptor);
+ listener.setFailOnError(key.isFailOnError());
+ return listener;
+ }
+
+ public static class KeyCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context,
+ AnnotatedTypeMetadata metadata) {
+ Environment environment = context.getEnvironment();
+ if (hasProperty(environment, "encrypt.keyStore.location")) {
+ if (hasProperty(environment, "encrypt.keyStore.password")) {
+ return ConditionOutcome.match("Keystore found in Environment");
+ }
+ return ConditionOutcome
+ .noMatch("Keystore found but no password in Environment");
+ }
+ else if (hasProperty(environment, "encrypt.key")) {
+ return ConditionOutcome.match("Key found in Environment");
+ }
+ return ConditionOutcome.noMatch("Keystore nor key found in Environment");
+ }
+
+ private boolean hasProperty(Environment environment, String key) {
+ String value = environment.getProperty(key);
+ if (value == null) {
+ return false;
+ }
+ return StringUtils.hasText(environment.resolvePlaceholders(value));
+ }
+
+ }
+
+ /**
+ * TextEncryptor that just fails, so that users don't get a false sense of security
+ * adding ciphers to config files and not getting them decrypted.
+ *
+ * @author Dave Syer
+ *
+ */
+ protected static class FailsafeTextEncryptor implements TextEncryptor {
+
+ @Override
+ public String encrypt(String text) {
+ throw new UnsupportedOperationException(
+ "No encryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
+ }
+
+ @Override
+ public String decrypt(String encryptedText) {
+ throw new UnsupportedOperationException(
+ "No decryption for FailsafeTextEncryptor. Did you configure the keystore correctly?");
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java b/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java
new file mode 100644
index 00000000..163e71d8
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.cloud.bootstrap.encrypt;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+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.PropertySource;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class EnvironmentDecryptApplicationInitializer implements
+ ApplicationContextInitializer, Ordered {
+
+ private static Log logger = LogFactory
+ .getLog(EnvironmentDecryptApplicationInitializer.class);
+
+ private int order = Ordered.HIGHEST_PRECEDENCE + 15;
+
+ private TextEncryptor encryptor;
+
+ private boolean failOnError = true;
+
+ public EnvironmentDecryptApplicationInitializer(TextEncryptor encryptor) {
+ this.encryptor = encryptor;
+ }
+
+ /**
+ * Strategy to determine how to handle exceptions during decryption.
+ *
+ * @param failOnError the flag value (default true)
+ */
+ public void setFailOnError(boolean failOnError) {
+ this.failOnError = failOnError;
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ @Override
+ public void initialize(ConfigurableApplicationContext applicationContext) {
+
+ ConfigurableEnvironment environment = applicationContext.getEnvironment();
+ Map overrides = new LinkedHashMap();
+ for (PropertySource> source : environment.getPropertySources()) {
+ decrypt(source, overrides);
+ }
+ if (!overrides.isEmpty()) {
+ environment.getPropertySources().addFirst(
+ new MapPropertySource("decrypted", overrides));
+ }
+ }
+
+ private void decrypt(PropertySource> source, Map overrides) {
+
+ if (source instanceof EnumerablePropertySource) {
+
+ EnumerablePropertySource> enumerable = (EnumerablePropertySource>) source;
+ for (String key : enumerable.getPropertyNames()) {
+ String value = source.getProperty(key).toString();
+ if (value.startsWith("{cipher}")) {
+ value = value.substring("{cipher}".length());
+ try {
+ value = encryptor.decrypt(value);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Decrypted: key=" + key);
+ }
+ }
+ catch (Exception e) {
+ String message = "Cannot decrypt: key=" + key;
+ if (failOnError) {
+ throw new IllegalStateException(message, e);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.warn(message, e);
+ }
+ else {
+ logger.warn(message);
+ }
+ // Set value to empty to avoid making a password out of the
+ // cipher text
+ value = "";
+ }
+ overrides.put(key, value);
+ }
+ }
+
+ }
+ else if (source instanceof CompositePropertySource) {
+
+ for (PropertySource> nested : ((CompositePropertySource) source)
+ .getPropertySources()) {
+ decrypt(nested, overrides);
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/bootstrap/encrypt/KeyProperties.java b/src/main/java/org/springframework/cloud/bootstrap/encrypt/KeyProperties.java
new file mode 100644
index 00000000..d3d3b11e
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/bootstrap/encrypt/KeyProperties.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cloud.bootstrap.encrypt;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.Resource;
+
+@ConfigurationProperties("encrypt")
+public class KeyProperties {
+
+ private String key;
+
+ private boolean failOnError = true;
+
+ private KeyProperties.KeyStore keyStore = new KeyStore();
+
+ public boolean isFailOnError() {
+ return failOnError;
+ }
+
+ public void setFailOnError(boolean failOnError) {
+ this.failOnError = failOnError;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public KeyStore getKeyStore() {
+ return keyStore;
+ }
+
+ public void setKeyStore(KeyProperties.KeyStore keyStore) {
+ this.keyStore = keyStore;
+ }
+
+ public static class KeyStore {
+
+ private Resource location;
+ private String password;
+ private String alias;
+ private String secret;
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public Resource getLocation() {
+ return location;
+ }
+
+ public void setLocation(Resource location) {
+ this.location = location;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getSecret() {
+ return secret==null ? password : secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = secret;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/springframework/cloud/context/config/BeanLifecycleDecorator.java b/src/main/java/org/springframework/cloud/context/config/BeanLifecycleDecorator.java
new file mode 100644
index 00000000..1488d095
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/config/BeanLifecycleDecorator.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2011 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.config;
+
+
+/**
+ * A helper interface providing optional decoration of bean instances and their
+ * destruction callbacks. Users can supply custom implementations of this
+ * strategy if they want tighter control over method invocation on the bean or
+ * its destruction callback.
+ *
+ * @param
+ * the type of auxiliary context object that can be passed between
+ * methods. Implementations can choose what type of data to supply as
+ * it is passed around unchanged by the caller.
+ *
+ * @author Dave Syer
+ *
+ */
+public interface BeanLifecycleDecorator {
+
+ /**
+ * Optionally decorate and provide a new instance of a compatible bean for
+ * the caller to use instead of the input.
+ *
+ * @param bean
+ * the bean to optionally decorate
+ * @param context
+ * the context as created by
+ * {@link #decorateDestructionCallback(Runnable)}
+ * @return the replacement bean for the caller to use
+ */
+ Object decorateBean(Object bean, Context context);
+
+ /**
+ * Optionally decorate the destruction callback provided, and also return
+ * some context that can be used later by the
+ * {@link #decorateBean(Object, Context)} method.
+ *
+ * @param callback
+ * the destruction callback that will be used by the container
+ * @return a context wrapper
+ */
+ Context decorateDestructionCallback(Runnable callback);
+
+ static class Context {
+
+ private final T auxiliary;
+ private final Runnable callback;
+
+ public Context(Runnable callback, T auxiliary) {
+ this.callback = callback;
+ this.auxiliary = auxiliary;
+ }
+
+ public Runnable getCallback() {
+ return callback;
+ }
+
+ public T getAuxiliary() {
+ return auxiliary;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/config/StandardBeanLifecycleDecorator.java b/src/main/java/org/springframework/cloud/context/config/StandardBeanLifecycleDecorator.java
new file mode 100644
index 00000000..32736f61
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/config/StandardBeanLifecycleDecorator.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2011 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.config;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.ProxyFactory;
+
+/**
+ * A {@link BeanLifecycleDecorator} that tries to protect against concurrent access to a bean during its own destruction.
+ * A read-write lock is used, and method access is protected using the read lock, while the destruction callback is
+ * protected more strictly with the write lock. In this way concurrent access is possible to the bean as long as it is
+ * not being destroyed, in which case only one thread has access. If the bean has no destruction callback the lock and
+ * associated proxies are never created.
+ *
+ * @author Dave Syer
+ *
+ */
+public class StandardBeanLifecycleDecorator implements BeanLifecycleDecorator {
+
+ private final boolean proxyTargetClass;
+
+ public StandardBeanLifecycleDecorator(boolean proxyTargetClass) {
+ this.proxyTargetClass = proxyTargetClass;
+ }
+
+ public Object decorateBean(Object bean, Context context) {
+ if (context != null) {
+ bean = getDisposalLockProxy(bean, context.getAuxiliary().readLock());
+ }
+ return bean;
+ }
+
+ public Context decorateDestructionCallback(final Runnable callback) {
+ if (callback == null) {
+ return null;
+ }
+ final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+ return new Context(new Runnable() {
+ public void run() {
+ Lock lock = readWriteLock.writeLock();
+ lock.lock();
+ try {
+ callback.run();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }, readWriteLock);
+ }
+
+ /**
+ * Apply a lock (preferably a read lock allowing multiple concurrent access) to the bean. Callers should replace the
+ * bean input with the output.
+ *
+ * @param bean the bean to lock
+ * @param lock the lock to apply
+ * @return a proxy that locks while its methods are executed
+ */
+ private Object getDisposalLockProxy(Object bean, final Lock lock) {
+ ProxyFactory factory = new ProxyFactory(bean);
+ factory.setProxyTargetClass(proxyTargetClass);
+ factory.addAdvice(new MethodInterceptor() {
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ lock.lock();
+ try {
+ return invocation.proceed();
+ } finally {
+ lock.unlock();
+ }
+ }
+ });
+ return factory.getProxy();
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/config/annotation/RefreshScope.java b/src/main/java/org/springframework/cloud/context/config/annotation/RefreshScope.java
new file mode 100644
index 00000000..d46daa24
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/config/annotation/RefreshScope.java
@@ -0,0 +1,47 @@
+/*
+ * 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.cloud.context.config.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+
+/**
+ * Convenience annotation to put a @Bean definition in
+ * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
+ * Beans annotated this way can be refreshed at runtime and any components that are using
+ * them will get a new instance on the next method call, fully initialized and injected
+ * with all dependencies.
+ *
+ * @author Dave Syer
+ *
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Scope("refresh")
+@Documented
+public @interface RefreshScope {
+ /**
+ * @see Scope#proxyMode()
+ */
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/encrypt/EncryptorFactory.java b/src/main/java/org/springframework/cloud/context/encrypt/EncryptorFactory.java
new file mode 100644
index 00000000..7e306ea9
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/encrypt/EncryptorFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cloud.context.encrypt;
+
+import org.springframework.security.crypto.encrypt.Encryptors;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class EncryptorFactory {
+
+ // TODO: expose as config property
+ private static final String SALT = "deadbeef";
+
+ public TextEncryptor create(String data) {
+
+ TextEncryptor encryptor;
+ if (data.contains("RSA PRIVATE KEY")) {
+
+ try {
+ encryptor = new RsaSecretEncryptor(data);
+ }
+ catch (IllegalArgumentException e) {
+ throw new KeyFormatException();
+ }
+
+ }
+ else if (data.startsWith("ssh-rsa") || data.contains("RSA PUBLIC KEY")) {
+ throw new KeyFormatException();
+ }
+ else {
+ encryptor = Encryptors.text(data, SALT);
+ }
+
+ return encryptor;
+ }
+
+}
+
+
diff --git a/src/main/java/org/springframework/cloud/context/encrypt/KeyFormatException.java b/src/main/java/org/springframework/cloud/context/encrypt/KeyFormatException.java
new file mode 100644
index 00000000..08e6fa47
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/encrypt/KeyFormatException.java
@@ -0,0 +1,20 @@
+/*
+ * 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.cloud.context.encrypt;
+
+@SuppressWarnings("serial")
+public class KeyFormatException extends RuntimeException {
+}
\ No newline at end of file
diff --git a/src/main/java/org/springframework/cloud/context/environment/EnvironmentChangeEvent.java b/src/main/java/org/springframework/cloud/context/environment/EnvironmentChangeEvent.java
new file mode 100644
index 00000000..d0fb5e50
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/environment/EnvironmentChangeEvent.java
@@ -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.cloud.context.environment;
+
+import java.util.Set;
+
+import org.springframework.context.ApplicationEvent;
+import org.springframework.core.env.Environment;
+
+/**
+ * Event published to signal a change in the {@link Environment}.
+ *
+ * @author Dave Syer
+ *
+ */
+@SuppressWarnings("serial")
+public class EnvironmentChangeEvent extends ApplicationEvent {
+
+ private Set keys;
+
+ public EnvironmentChangeEvent(Set keys) {
+ super(keys);
+ this.keys = keys;
+ }
+
+ /**
+ * @return the keys
+ */
+ public Set getKeys() {
+ return keys;
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/environment/EnvironmentManager.java b/src/main/java/org/springframework/cloud/context/environment/EnvironmentManager.java
new file mode 100644
index 00000000..91e625cf
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/environment/EnvironmentManager.java
@@ -0,0 +1,109 @@
+/*
+ * 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.cloud.context.environment;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.stereotype.Component;
+
+/**
+ * Entry point for making local (but volatile) changes to the {@link Environment} of a
+ * running application. Allows properties to be added and values changed, simply by adding
+ * them to a high priority property source in the existing Environment.
+ *
+ * @author Dave Syer
+ *
+ */
+@Component
+@ManagedResource
+public class EnvironmentManager implements ApplicationEventPublisherAware {
+
+ private static final String MANAGER_PROPERTY_SOURCE = "manager";
+ private Map map = new LinkedHashMap();
+
+ private ConfigurableEnvironment environment;
+ private ApplicationEventPublisher publisher;
+
+ public EnvironmentManager(ConfigurableEnvironment environment) {
+ this.environment = environment;
+ MutablePropertySources sources = environment.getPropertySources();
+ if (sources.contains(MANAGER_PROPERTY_SOURCE)) {
+ @SuppressWarnings("unchecked")
+ Map map = (Map) sources.get(
+ MANAGER_PROPERTY_SOURCE).getSource();
+ this.map = map;
+ }
+ }
+
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+ this.publisher = publisher;
+ }
+
+ @ManagedOperation
+ public Map reset() {
+ Map result = new LinkedHashMap(map);
+ if (!map.isEmpty()) {
+ Set keys = map.keySet();
+ map.clear();
+ publish(new EnvironmentChangeEvent(keys));
+ }
+ return result;
+ }
+
+ @ManagedOperation
+ public void setProperty(String name, String value) {
+
+ if (!environment.getPropertySources().contains(MANAGER_PROPERTY_SOURCE)) {
+ synchronized (map) {
+ if (!environment.getPropertySources().contains(MANAGER_PROPERTY_SOURCE)) {
+ MapPropertySource source = new MapPropertySource(
+ MANAGER_PROPERTY_SOURCE, map);
+ environment.getPropertySources().addFirst(source);
+ }
+ }
+ }
+
+ if (!value.equals(environment.getProperty(name))) {
+ map.put(name, value);
+ publish(new EnvironmentChangeEvent(Collections.singleton(name)));
+ }
+
+ }
+
+ @ManagedOperation
+ public Object getProperty(String name) {
+ return environment.getProperty(name);
+ }
+
+ private void publish(EnvironmentChangeEvent environmentChangeEvent) {
+ if (publisher != null) {
+ publisher.publishEvent(environmentChangeEvent);
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/environment/EnvironmentManagerMvcEndpoint.java b/src/main/java/org/springframework/cloud/context/environment/EnvironmentManagerMvcEndpoint.java
new file mode 100644
index 00000000..652b08bd
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/environment/EnvironmentManagerMvcEndpoint.java
@@ -0,0 +1,80 @@
+/*
+ * 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.cloud.context.environment;
+
+import java.util.Map;
+
+import org.springframework.boot.actuate.endpoint.Endpoint;
+import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * MVC endpoint for the {@link EnvironmentManager} providing a POST to /env as a simple
+ * way to change the Environment.
+ *
+ * @author Dave Syer
+ *
+ */
+public class EnvironmentManagerMvcEndpoint implements MvcEndpoint {
+
+ private EnvironmentManager environment;
+ private EnvironmentEndpoint delegate;
+
+ public EnvironmentManagerMvcEndpoint(EnvironmentEndpoint delegate,
+ EnvironmentManager enviroment) {
+ this.delegate = delegate;
+ environment = enviroment;
+ }
+
+ @RequestMapping(value = "", method = RequestMethod.POST)
+ @ResponseBody
+ public Object value(@RequestParam Map params) {
+ for (String name : params.keySet()) {
+ environment.setProperty(name, params.get(name));
+ }
+ return params;
+ }
+
+ @RequestMapping(value = "reset", method = RequestMethod.POST)
+ @ResponseBody
+ public Map reset() {
+ return environment.reset();
+ }
+
+ public void setEnvironmentManager(EnvironmentManager environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public String getPath() {
+ return "/" + this.delegate.getId();
+ }
+
+ @Override
+ public boolean isSensitive() {
+ return this.delegate.isSensitive();
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Class extends Endpoint> getEndpointType() {
+ return this.delegate.getClass();
+ }
+}
diff --git a/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java b/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java
new file mode 100644
index 00000000..c0799857
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java
@@ -0,0 +1,133 @@
+/*
+ * 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.cloud.context.properties;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.env.Environment;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.stereotype.Component;
+
+/**
+ * Listens for {@link EnvironmentChangeEvent} and rebinds beans that were bound to the
+ * {@link Environment} using {@link ConfigurationProperties
+ * @ConfigurationProperties}. When these beans are re-bound and
+ * re-initialized the changes are available immediately to any component that is using the
+ * @ConfigurationProperties bean.
+ *
+ * @see RefreshScope for a deeper and optionally more focused refresh of bean components
+ *
+ * @author Dave Syer
+ *
+ */
+@Component
+@ManagedResource
+public class ConfigurationPropertiesRebinder implements BeanPostProcessor,
+ ApplicationListener, ApplicationContextAware {
+
+ private ConfigurationBeanFactoryMetaData metaData;
+
+ private ConfigurationPropertiesBindingPostProcessor binder;
+
+ public ConfigurationPropertiesRebinder(
+ ConfigurationPropertiesBindingPostProcessor binder) {
+ this.binder = binder;
+ }
+
+ private Map beans = new HashMap();
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext)
+ throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * @param beans the bean meta data to set
+ */
+ public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
+ this.metaData = beans;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
+ bean.getClass(), ConfigurationProperties.class);
+ if (annotation != null) {
+ beans.put(beanName, bean);
+ }
+ else if (metaData != null) {
+ annotation = this.metaData.findFactoryAnnotation(beanName,
+ ConfigurationProperties.class);
+ if (annotation != null) {
+ beans.put(beanName, bean);
+ }
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ return bean;
+ }
+
+ @ManagedOperation
+ public void rebind() {
+ for (String name : beans.keySet()) {
+ rebind(name);
+ }
+ }
+
+ @ManagedOperation
+ public void rebind(String name) {
+ binder.postProcessBeforeInitialization(beans.get(name), name);
+ if (applicationContext != null) {
+ applicationContext.getAutowireCapableBeanFactory().initializeBean(
+ beans.get(name), name);
+ }
+ }
+
+ @ManagedAttribute
+ public Set getBeanNames() {
+ return new HashSet(beans.keySet());
+ }
+
+ @Override
+ public void onApplicationEvent(EnvironmentChangeEvent event) {
+ rebind();
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java b/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java
new file mode 100644
index 00000000..4e903f8e
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java
@@ -0,0 +1,206 @@
+/*
+ * 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.cloud.context.restart;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
+import org.springframework.boot.actuate.endpoint.Endpoint;
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.integration.monitor.IntegrationMBeanExporter;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.util.ClassUtils;
+
+/**
+ * An endpoint that restarts the application context. Install as a bean and also register
+ * a {@link RestartListener} with the {@link SpringApplication} that starts the context.
+ * Those two components communicate via an {@link ApplicationEvent} and set up the state
+ * needed to restart the context.
+ *
+ * @author Dave Syer
+ *
+ */
+@ConfigurationProperties("endpoints.restart")
+@ManagedResource
+public class RestartEndpoint extends AbstractEndpoint implements
+ ApplicationListener {
+
+ private static Log logger = LogFactory.getLog(RestartEndpoint.class);
+
+ public RestartEndpoint() {
+ super("restart", true, false);
+ }
+
+ private ConfigurableApplicationContext context;
+
+ private SpringApplication application;
+
+ private String[] args;
+
+ private ApplicationPreparedEvent event;
+
+ private IntegrationShutdown integrationShutdown;
+
+ private long timeout;
+
+ @ManagedAttribute
+ public long getTimeout() {
+ return this.timeout;
+ }
+
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ public void setIntegrationMBeanExporter(Object exporter) {
+ if (exporter != null) {
+ this.integrationShutdown = new IntegrationShutdown(exporter);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent input) {
+ this.event = input;
+ if (this.context == null) {
+ this.context = this.event.getApplicationContext();
+ this.args = this.event.getArgs();
+ this.application = this.event.getSpringApplication();
+ }
+ }
+
+ @Override
+ public Boolean invoke() {
+ try {
+ restart();
+ logger.info("Restarted");
+ return true;
+ }
+ catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.info("Could not restart", e);
+ }
+ else {
+ logger.info("Could not restart: " + e.getMessage());
+ }
+ return false;
+ }
+ }
+
+ public Endpoint getPauseEndpoint() {
+ return new PauseEndpoint();
+ }
+
+ public Endpoint getResumeEndpoint() {
+ return new ResumeEndpoint();
+ }
+
+ private class PauseEndpoint extends AbstractEndpoint {
+
+ public PauseEndpoint() {
+ super("pause", true, true);
+ }
+
+ @Override
+ public Boolean invoke() {
+ if (isRunning()) {
+ pause();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private class ResumeEndpoint extends AbstractEndpoint {
+
+ public ResumeEndpoint() {
+ super("resume", true, true);
+ }
+
+ @Override
+ public Boolean invoke() {
+ if (!isRunning()) {
+ resume();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ @ManagedOperation
+ public synchronized ConfigurableApplicationContext restart() {
+ if (this.context != null) {
+ if (this.integrationShutdown != null) {
+ this.integrationShutdown.stop(this.timeout);
+ }
+ this.application.setEnvironment(this.context.getEnvironment());
+ this.context.close();
+ // If running in a webapp then the context classloader is probably going to
+ // die so we need to revert to a safe place before starting again
+ overrideClassLoaderForRestart();
+ this.context = this.application.run(this.args);
+ }
+ return this.context;
+ }
+
+ @ManagedAttribute
+ public boolean isRunning() {
+ if (this.context != null) {
+ return this.context.isRunning();
+ }
+ return false;
+ }
+
+ @ManagedOperation
+ public synchronized void pause() {
+ if (this.context != null) {
+ this.context.stop();
+ }
+ }
+
+ @ManagedOperation
+ public synchronized void resume() {
+ if (this.context != null) {
+ this.context.start();
+ }
+ }
+
+ private void overrideClassLoaderForRestart() {
+ ClassUtils.overrideThreadContextClassLoader(this.application.getClass()
+ .getClassLoader());
+ }
+
+ private class IntegrationShutdown {
+
+ private IntegrationMBeanExporter exporter;
+
+ public IntegrationShutdown(Object exporter) {
+ this.exporter = (IntegrationMBeanExporter) exporter;
+ }
+
+ public void stop(long timeout) {
+ this.exporter.stopActiveComponents(timeout);
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/restart/RestartListener.java b/src/main/java/org/springframework/cloud/context/restart/RestartListener.java
new file mode 100644
index 00000000..4dcad0ad
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/restart/RestartListener.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cloud.context.restart;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.SmartApplicationListener;
+
+/**
+ * A listener that stores enough information about an application as it starts, to be able
+ * to restart it later if needed.
+ *
+ * @author Dave Syer
+ *
+ */
+public class RestartListener implements SmartApplicationListener {
+
+ private ConfigurableApplicationContext context;
+
+ private ApplicationPreparedEvent event;
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+
+ @Override
+ public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
+ return ApplicationPreparedEvent.class.isAssignableFrom(eventType)
+ || ContextRefreshedEvent.class.isAssignableFrom(eventType)
+ || ContextClosedEvent.class.isAssignableFrom(eventType);
+
+ }
+
+ @Override
+ public boolean supportsSourceType(Class> sourceType) {
+ return true;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationEvent input) {
+ if (input instanceof ApplicationPreparedEvent) {
+ event = (ApplicationPreparedEvent) input;
+ if (context == null) {
+ context = event.getApplicationContext();
+ }
+ }
+ else if (input instanceof ContextRefreshedEvent) {
+ if (context != null && input.getSource().equals(context) && event != null) {
+ context.publishEvent(event);
+ }
+ }
+ else {
+ if (context != null && input.getSource().equals(context)) {
+ context = null;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/cloud/context/restart/RestartMvcEndpoint.java b/src/main/java/org/springframework/cloud/context/restart/RestartMvcEndpoint.java
new file mode 100644
index 00000000..ab8b32e9
--- /dev/null
+++ b/src/main/java/org/springframework/cloud/context/restart/RestartMvcEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cloud.context.restart;
+
+import java.util.Collections;
+import java.util.Map;
+
+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.cloud.endpoint.GenericPostableMvcEndpoint;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * MVC endpoint to allow an application to be restarted on a POST (to /restart by
+ * default).
+ *
+ * @author Dave Syer
+ *
+ */
+public class RestartMvcEndpoint extends EndpointMvcAdapter {
+
+ public RestartMvcEndpoint(RestartEndpoint delegate) {
+ super(delegate);
+ }
+
+ @RequestMapping(method = RequestMethod.POST)
+ @ResponseBody
+ @Override
+ public Object invoke() {
+ if (!getDelegate().isEnabled()) {
+ return new ResponseEntity