Browse Source

Move all of non-config client related stuff into commons

Refresh, bootstrap, encryption all moved in to the common jar, for
now at least
pull/15/head
Dave Syer 10 years ago
parent
commit
0b0999fde8
  1. 19
      pom.xml
  2. 85
      src/main/java/org/springframework/cloud/autoconfigure/LifecycleMvcEndpointAutoConfiguration.java
  3. 216
      src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java
  4. 217
      src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java
  5. 38
      src/main/java/org/springframework/cloud/bootstrap/BootstrapConfiguration.java
  6. 127
      src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java
  7. 37
      src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java
  8. 39
      src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceLocator.java
  9. 165
      src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java
  10. 155
      src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java
  11. 127
      src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java
  12. 94
      src/main/java/org/springframework/cloud/bootstrap/encrypt/KeyProperties.java
  13. 80
      src/main/java/org/springframework/cloud/context/config/BeanLifecycleDecorator.java
  14. 94
      src/main/java/org/springframework/cloud/context/config/StandardBeanLifecycleDecorator.java
  15. 47
      src/main/java/org/springframework/cloud/context/config/annotation/RefreshScope.java
  16. 56
      src/main/java/org/springframework/cloud/context/encrypt/EncryptorFactory.java
  17. 20
      src/main/java/org/springframework/cloud/context/encrypt/KeyFormatException.java
  18. 46
      src/main/java/org/springframework/cloud/context/environment/EnvironmentChangeEvent.java
  19. 109
      src/main/java/org/springframework/cloud/context/environment/EnvironmentManager.java
  20. 80
      src/main/java/org/springframework/cloud/context/environment/EnvironmentManagerMvcEndpoint.java
  21. 133
      src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java
  22. 206
      src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java
  23. 77
      src/main/java/org/springframework/cloud/context/restart/RestartListener.java
  24. 72
      src/main/java/org/springframework/cloud/context/restart/RestartMvcEndpoint.java
  25. 367
      src/main/java/org/springframework/cloud/context/scope/GenericScope.java
  26. 65
      src/main/java/org/springframework/cloud/context/scope/ScopeCache.java
  27. 56
      src/main/java/org/springframework/cloud/context/scope/StandardScopeCache.java
  28. 93
      src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java
  29. 25
      src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScopeRefreshedEvent.java
  30. 61
      src/main/java/org/springframework/cloud/context/scope/thread/ThreadLocalScopeCache.java
  31. 36
      src/main/java/org/springframework/cloud/context/scope/thread/ThreadScope.java
  32. 53
      src/main/java/org/springframework/cloud/endpoint/GenericPostableMvcEndpoint.java
  33. 81
      src/main/java/org/springframework/cloud/logging/LoggingRebinder.java
  34. 15
      src/main/resources/META-INF/spring.factories
  35. 35
      src/test/java/org/springframework/cloud/bootstrap/BootstrapDisabledAutoConfigurationIntegrationTests.java
  36. 48
      src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java
  37. 323
      src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java
  38. 24
      src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfigurationTests.java
  39. 62
      src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationListenerTests.java
  40. 122
      src/test/java/org/springframework/cloud/context/environment/EnvironmentManagerIntegrationTests.java
  41. 114
      src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java
  42. 73
      src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java
  43. 67
      src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java
  44. 230
      src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java
  45. 114
      src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java
  46. 185
      src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeConcurrencyTests.java
  47. 169
      src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeConfigurationTests.java
  48. 229
      src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java
  49. 100
      src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeWebIntegrationTests.java
  50. 57
      src/test/java/org/springframework/cloud/logging/LoggingRebinderTests.java
  51. 1
      src/test/resources/application-encrypt.properties
  52. 5
      src/test/resources/application.properties
  53. 1
      src/test/resources/bootstrap-encrypt.properties
  54. 1
      src/test/resources/bootstrap-parent.properties
  55. 2
      src/test/resources/bootstrap.properties
  56. 1
      src/test/resources/external-properties/bootstrap.properties
  57. 1
      src/test/resources/other.properties
  58. 1
      src/test/resources/plain.properties
  59. BIN
      src/test/resources/server.jks

19
pom.xml

@ -64,6 +64,25 @@ @@ -64,6 +64,25 @@
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jmx</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

85
src/main/java/org/springframework/cloud/autoconfigure/LifecycleMvcEndpointAutoConfiguration.java

@ -0,0 +1,85 @@ @@ -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();
}
}

216
src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java

@ -0,0 +1,216 @@ @@ -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<EnvironmentChangeEvent> {
@Autowired
private EndpointAutoConfiguration endpoints;
@Autowired
private ConfigurableEnvironment environment;
private Map<String, Object> map = new LinkedHashMap<String, Object>();
@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<String, Object> invoke() {
Map<String, Object> info = new LinkedHashMap<String, Object>(
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<Boolean> pauseEndpoint(RestartEndpoint restartEndpoint) {
return restartEndpoint.getPauseEndpoint();
}
@Bean
@ConfigurationProperties("endpoints.resume")
public Endpoint<Boolean> 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;
}
}
}
}

217
src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java

@ -0,0 +1,217 @@ @@ -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<ApplicationEnvironmentPreparedEvent>, 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<String, Object> 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.<String, Object> emptyMap()));
for (PropertySource<?> source : environment.getPropertySources()) {
bootstrapProperties.addLast(source);
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> 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<Class<?>> 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<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
ApplicationContextInitializer.class);
application.addInitializers(initializers
.toArray(new ApplicationContextInitializer[initializers.size()]));
}
private <T> List<T> getOrderedBeansOfType(ListableBeanFactory context, Class<T> type) {
List<T> result = new ArrayList<T>();
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<ConfigurableApplicationContext>, 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
.<String, Object> 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);
}
}
}
}

38
src/main/java/org/springframework/cloud/bootstrap/BootstrapConfiguration.java

@ -0,0 +1,38 @@ @@ -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 <code>META-INF/spring.factories</code>. 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 {};
}

127
src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java

@ -0,0 +1,127 @@ @@ -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<ConfigurableApplicationContext> {
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<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
@Autowired
private PropertySourceBootstrapProperties properties;
public void setPropertySourceLocators(
Collection<PropertySourceLocator> 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
.<String> 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);
}
}
}

37
src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapProperties.java

@ -0,0 +1,37 @@ @@ -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;
}
}

39
src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceLocator.java

@ -0,0 +1,39 @@ @@ -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);
}

165
src/main/java/org/springframework/cloud/bootstrap/config/RefreshEndpoint.java

@ -0,0 +1,165 @@ @@ -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<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 RefreshScope scope;
public RefreshEndpoint(ConfigurableApplicationContext context, RefreshScope scope) {
super("refresh");
this.context = context;
this.scope = scope;
}
@ManagedOperation
public synchronized String[] refresh() {
Map<String, Object> before = extract(context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> 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<String> invoke() {
return Arrays.asList(refresh());
}
private Map<String, Object> changes(Map<String, Object> before,
Map<String, Object> after) {
Map<String, Object> result = new HashMap<String, Object>();
for (String key : before.keySet()) {
if (!after.containsKey(key)) {
result.put(key, null);
} else if (!equal(before.get(key), after.get(key))) {
result.put(key, after.get(key));
}
}
for (String key : after.keySet()) {
if (!before.containsKey(key)) {
result.put(key, after.get(key));
}
}
return result;
}
private boolean equal(Object one, Object two) {
if (one == null && two == null) {
return true;
}
if (one == null || two == null) {
return false;
}
return one.equals(two);
}
private Map<String, Object> extract(MutablePropertySources propertySources) {
Map<String, Object> result = new HashMap<String, Object>();
for (PropertySource<?> parent : propertySources) {
if (!standardSources.contains(parent.getName())) {
extract(parent, result);
}
}
return result;
}
private void extract(PropertySource<?> parent, Map<String, Object> 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 {
}
}

155
src/main/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfiguration.java

@ -0,0 +1,155 @@ @@ -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?");
}
}
}

127
src/main/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationInitializer.java

@ -0,0 +1,127 @@ @@ -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<ConfigurableApplicationContext>, 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<String, Object> overrides = new LinkedHashMap<String, Object>();
for (PropertySource<?> source : environment.getPropertySources()) {
decrypt(source, overrides);
}
if (!overrides.isEmpty()) {
environment.getPropertySources().addFirst(
new MapPropertySource("decrypted", overrides));
}
}
private void decrypt(PropertySource<?> source, Map<String, Object> 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);
}
}
}
}

94
src/main/java/org/springframework/cloud/bootstrap/encrypt/KeyProperties.java

@ -0,0 +1,94 @@ @@ -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;
}
}
}

80
src/main/java/org/springframework/cloud/context/config/BeanLifecycleDecorator.java

@ -0,0 +1,80 @@ @@ -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 <T>
* 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<T> {
/**
* 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<T> 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<T> decorateDestructionCallback(Runnable callback);
static class Context<T> {
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;
}
}
}

94
src/main/java/org/springframework/cloud/context/config/StandardBeanLifecycleDecorator.java

@ -0,0 +1,94 @@ @@ -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<ReadWriteLock> {
private final boolean proxyTargetClass;
public StandardBeanLifecycleDecorator(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
public Object decorateBean(Object bean, Context<ReadWriteLock> context) {
if (context != null) {
bean = getDisposalLockProxy(bean, context.getAuxiliary().readLock());
}
return bean;
}
public Context<ReadWriteLock> decorateDestructionCallback(final Runnable callback) {
if (callback == null) {
return null;
}
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
return new Context<ReadWriteLock>(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();
}
}

47
src/main/java/org/springframework/cloud/context/config/annotation/RefreshScope.java

@ -0,0 +1,47 @@ @@ -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 <code>@Bean</code> 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;
}

56
src/main/java/org/springframework/cloud/context/encrypt/EncryptorFactory.java

@ -0,0 +1,56 @@ @@ -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;
}
}

20
src/main/java/org/springframework/cloud/context/encrypt/KeyFormatException.java

@ -0,0 +1,20 @@ @@ -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 {
}

46
src/main/java/org/springframework/cloud/context/environment/EnvironmentChangeEvent.java

@ -0,0 +1,46 @@ @@ -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<String> keys;
public EnvironmentChangeEvent(Set<String> keys) {
super(keys);
this.keys = keys;
}
/**
* @return the keys
*/
public Set<String> getKeys() {
return keys;
}
}

109
src/main/java/org/springframework/cloud/context/environment/EnvironmentManager.java

@ -0,0 +1,109 @@ @@ -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<String, Object> map = new LinkedHashMap<String, Object>();
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<String, Object> map = (Map<String, Object>) sources.get(
MANAGER_PROPERTY_SOURCE).getSource();
this.map = map;
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@ManagedOperation
public Map<String, Object> reset() {
Map<String, Object> result = new LinkedHashMap<String, Object>(map);
if (!map.isEmpty()) {
Set<String> 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);
}
}
}

80
src/main/java/org/springframework/cloud/context/environment/EnvironmentManagerMvcEndpoint.java

@ -0,0 +1,80 @@ @@ -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<String, String> params) {
for (String name : params.keySet()) {
environment.setProperty(name, params.get(name));
}
return params;
}
@RequestMapping(value = "reset", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> 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();
}
}

133
src/main/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinder.java

@ -0,0 +1,133 @@ @@ -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
* <code>@ConfigurationProperties</code>}. When these beans are re-bound and
* re-initialized the changes are available immediately to any component that is using the
* <code>@ConfigurationProperties</code> 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<EnvironmentChangeEvent>, ApplicationContextAware {
private ConfigurationBeanFactoryMetaData metaData;
private ConfigurationPropertiesBindingPostProcessor binder;
public ConfigurationPropertiesRebinder(
ConfigurationPropertiesBindingPostProcessor binder) {
this.binder = binder;
}
private Map<String, Object> beans = new HashMap<String, Object>();
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<String> getBeanNames() {
return new HashSet<String>(beans.keySet());
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
rebind();
}
}

206
src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java

@ -0,0 +1,206 @@ @@ -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<Boolean> implements
ApplicationListener<ApplicationPreparedEvent> {
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<Boolean> getPauseEndpoint() {
return new PauseEndpoint();
}
public Endpoint<Boolean> getResumeEndpoint() {
return new ResumeEndpoint();
}
private class PauseEndpoint extends AbstractEndpoint<Boolean> {
public PauseEndpoint() {
super("pause", true, true);
}
@Override
public Boolean invoke() {
if (isRunning()) {
pause();
return true;
}
return false;
}
}
private class ResumeEndpoint extends AbstractEndpoint<Boolean> {
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);
}
}
}

77
src/main/java/org/springframework/cloud/context/restart/RestartListener.java

@ -0,0 +1,77 @@ @@ -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;
}
}
}
}

72
src/main/java/org/springframework/cloud/context/restart/RestartMvcEndpoint.java

@ -0,0 +1,72 @@ @@ -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<Map<String, String>>(Collections.singletonMap(
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
RestartMvcEndpoint.super.invoke();
}
});
thread.setDaemon(false);
thread.start();
return Collections.singletonMap("message", "Restarting");
}
public MvcEndpoint getPauseEndpoint() {
return new GenericPostableMvcEndpoint(
((RestartEndpoint) getDelegate()).getPauseEndpoint());
}
public MvcEndpoint getResumeEndpoint() {
return new GenericPostableMvcEndpoint(
((RestartEndpoint) getDelegate()).getResumeEndpoint());
}
}

367
src/main/java/org/springframework/cloud/context/scope/GenericScope.java

@ -0,0 +1,367 @@ @@ -0,0 +1,367 @@
/*
* Copyright 2002-2009 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.scope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.cloud.context.config.BeanLifecycleDecorator;
import org.springframework.cloud.context.config.BeanLifecycleDecorator.Context;
import org.springframework.cloud.context.config.StandardBeanLifecycleDecorator;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;
/**
* <p>
* A generic Scope implementation.
* </p>
*
* @author Dave Syer
*
* @since 3.1
*
*/
public class GenericScope implements Scope, BeanFactoryPostProcessor, DisposableBean {
private static final Log logger = LogFactory.getLog(GenericScope.class);
public static final String SCOPED_TARGET_PREFIX = "scopedTarget.";
private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
new StandardScopeCache());
private String name = "generic";
private boolean proxyTargetClass = true;
private ConfigurableListableBeanFactory beanFactory;
private StandardEvaluationContext evaluationContext;
private String id;
private BeanLifecycleDecorator<?> lifecycle;
/**
* Manual override for the serialization id that will be used to identify the bean
* factory. The default is a unique key based on the bean names in the bean factory.
*
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* The name of this scope. Default "refresh".
*
* @param name the name value to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Flag to indicate that proxies should be created for the concrete type, not just the
* interfaces, of the scoped beans.
*
* @param proxyTargetClass the flag value to set
*/
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
/**
* The cache implementation to use for bean instances in this scope.
*
* @param cache the cache to use
*/
public void setScopeCache(ScopeCache cache) {
this.cache = new BeanLifecycleWrapperCache(cache);
}
/**
* Helper to manage the creation and destruction of beans.
*
* @param lifecycle the bean lifecycle to set
*/
public void setBeanLifecycleManager(BeanLifecycleDecorator<?> lifecycle) {
this.lifecycle = lifecycle;
}
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
wrapper.destroy();
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
}
protected void destroy(String name) {
BeanLifecycleWrapper wrapper = this.cache.remove(name);
if (wrapper != null) {
wrapper.destroy();
}
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (this.lifecycle == null) {
this.lifecycle = new StandardBeanLifecycleDecorator(this.proxyTargetClass);
}
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name,
objectFactory, this.lifecycle));
return value.getBean();
}
@Override
public String getConversationId() {
return this.name;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
BeanLifecycleWrapper value = this.cache.get(name);
if (value == null) {
return;
}
value.setDestroyCallback(callback);
}
@Override
public Object remove(String name) {
BeanLifecycleWrapper value = this.cache.remove(name);
if (value == null) {
return null;
}
// Someone might have added another object with the same key, but we
// keep the method contract by removing the
// value we found anyway
return value.getBean();
}
@Override
public Object resolveContextualObject(String key) {
Expression expression = parseExpression(key);
return expression.getValue(this.evaluationContext, this.beanFactory);
}
private Expression parseExpression(String input) {
if (StringUtils.hasText(input)) {
ExpressionParser parser = new SpelExpressionParser();
try {
return parser.parseExpression(input);
}
catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse expression: " + input, e);
}
}
else {
return null;
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
/**
* If the bean factory is a DefaultListableBeanFactory then it can serialize scoped
* beans and deserialize them in another context (even in another JVM), as long as the
* ids of the bean factories match. This method sets up the serialization id to be
* either the id provided to the scope instance, or if that is null, a hash of all the
* bean names.
*
* @param beanFactory the bean factory to configure
*/
private void setSerializationId(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof DefaultListableBeanFactory) {
String id = this.id;
if (id == null) {
String names = Arrays.asList(beanFactory.getBeanDefinitionNames())
.toString();
logger.debug("Generating bean factory id from names: " + names);
id = UUID.nameUUIDFromBytes(names.getBytes()).toString();
}
logger.info("BeanFactory id=" + id);
((DefaultListableBeanFactory) beanFactory).setSerializationId(id);
}
else {
logger.warn("BeanFactory was not a DefaultListableBeanFactory, so RefreshScope beans "
+ "cannot be serialized reliably and passed to a remote JVM.");
}
}
static RuntimeException wrapIfNecessary(Throwable throwable) {
if (throwable instanceof RuntimeException) {
return (RuntimeException) throwable;
}
if (throwable instanceof Error) {
throw (Error) throwable;
}
return new IllegalStateException(throwable);
}
private static class BeanLifecycleWrapperCache {
private final ScopeCache cache;
public BeanLifecycleWrapperCache(ScopeCache cache) {
this.cache = cache;
}
public BeanLifecycleWrapper remove(String name) {
return (BeanLifecycleWrapper) this.cache.remove(name);
}
public Collection<BeanLifecycleWrapper> clear() {
Collection<Object> values = this.cache.clear();
Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>();
for (Object object : values) {
wrappers.add((BeanLifecycleWrapper) object);
}
return wrappers;
}
public BeanLifecycleWrapper get(String name) {
return (BeanLifecycleWrapper) this.cache.get(name);
}
public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
return (BeanLifecycleWrapper) this.cache.put(name, value);
}
}
/**
* Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that
* is registered for it. Also decorates the bean to optionally guard it from
* concurrent access (for instance).
*
* @author Dave Syer
*
*/
private static class BeanLifecycleWrapper {
private Object bean;
private Context<?> context;
private final String name;
@SuppressWarnings("rawtypes")
private final BeanLifecycleDecorator lifecycle;
private final ObjectFactory<?> objectFactory;
@SuppressWarnings("rawtypes")
public BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory,
BeanLifecycleDecorator lifecycle) {
this.name = name;
this.objectFactory = objectFactory;
this.lifecycle = lifecycle;
}
public void setDestroyCallback(Runnable callback) {
this.context = this.lifecycle.decorateDestructionCallback(callback);
}
@SuppressWarnings("unchecked")
public Object getBean() {
if (this.bean == null) {
this.bean = this.lifecycle.decorateBean(this.objectFactory.getObject(),
this.context);
}
return this.bean;
}
public void destroy() {
if (this.context == null) {
return;
}
Runnable callback = this.context.getCallback();
if (callback != null) {
callback.run();
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj;
if (this.name == null) {
if (other.name != null) {
return false;
}
}
else if (!this.name.equals(other.name)) {
return false;
}
return true;
}
}
}

65
src/main/java/org/springframework/cloud/context/scope/ScopeCache.java

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/*
* 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.scope;
import java.util.Collection;
/**
* A special purpose cache interface specifically for the {@link GenericScope} to use to manage cached bean instances.
* Implementations generally fall into two categories: those that store values "globally" (i.e. one instance per key),
* and those that store potentially multiple instances per key based on context (e.g. via a thread local). All
* implementations should be thread safe.
*
* @author Dave Syer
*
*/
public interface ScopeCache {
/**
* Remove the object with this name from the cache.
*
* @param name the object name
* @return the object removed or null if there was none
*/
Object remove(String name);
/**
* Clear the cache and return all objects in an unmodifiable collection.
*
* @return all objects stored in the cache
*/
Collection<Object> clear();
/**
* Get the named object from the cache.
*
* @param name the name of the object
* @return the object with that name or null if there is none
*/
Object get(String name);
/**
* Put a value in the cache if the key is not already used. If one is already present with the name provided, it is
* not replaced, but is returned to the caller.
*
* @param name the key
* @param value the new candidate value
* @return the value that is in the cache at the end of the operation
*/
Object put(String name, Object value);
}

56
src/main/java/org/springframework/cloud/context/scope/StandardScopeCache.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/*
* 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.scope;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A simple cache implementation backed by a concurrent map.
*
* @author Dave Syer
*
*/
public class StandardScopeCache implements ScopeCache {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
public Object remove(String name) {
return cache.remove(name);
}
public Collection<Object> clear() {
Collection<Object> values = new ArrayList<Object>(cache.values());
cache.clear();
return values;
}
public Object get(String name) {
return cache.get(name);
}
public Object put(String name, Object value) {
Object result = cache.putIfAbsent(name, value);
if (result!=null) {
return result;
}
return value;
}
}

93
src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 2002-2009 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.scope.refresh;
import java.io.Serializable;
import org.springframework.beans.BeansException;
import org.springframework.cloud.context.scope.GenericScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
* <p>
* A Scope implementation that allows for beans to be refreshed dynamically at runtime (see {@link #refresh(String)} and
* {@link #refreshAll()}). If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a
* new instance is created. All lifecycle methods are applied to the bean instances, so any destruction callbacks that
* were registered in the bean factory are called when it is refreshed, and then the initialization callbacks are
* invoked as normal when the new instance is created. A new bean instance is created from the original bean definition,
* so any externalized content (property placeholders or expressions in string literals) is re-evaluated when it is
* created.
* </p>
*
* <p>
* Note that all beans in this scope are <em>only</em> initialized when first accessed, so the scope forces lazy
* initialization semantics. The implementation involves creating a proxy for every bean in the scope, so there is a
* flag {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy creation, defaulting to JDK
* dynamic proxies and therefore only exposing the interfaces implemented by a bean. If callers need access to other
* methods then the flag needs to be set (and CGLib present on the classpath). Because this scope automatically proxies
* all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean definitions.
* </p>
*
* <p>
* The scoped proxy approach adopted here has a side benefit that bean instances are automatically {@link Serializable},
* and can be sent across the wire as long as the receiver has an identical application context on the other side. To
* ensure that the two contexts agree that they are identical they have to have the same serialization id. One will be
* generated automatically by default from the bean names, so two contexts with the same bean names are by default able
* to exchange beans by name. If you need to override the default id then provide an explicit {@link #setId(String) id}
* when the Scope is declared.
* </p>
*
* @author Dave Syer
*
* @since 3.1
*
*/
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware {
private ApplicationContext context;
/**
* Create a scope instance and give it the default name: "refresh".
*/
public RefreshScope() {
super();
super.setName("refresh");
}
@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
public void refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
// User wants to refresh the bean with this name but that isn't the one in the cache...
name = SCOPED_TARGET_PREFIX + name;
}
// Ensure lifecycle is finished if bean was disposable
super.destroy(name);
context.publishEvent(new RefreshScopeRefreshedEvent(name));
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
}

25
src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScopeRefreshedEvent.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package org.springframework.cloud.context.scope.refresh;
import org.springframework.context.ApplicationEvent;
/**
* @author Spencer Gibb
*/
@SuppressWarnings("serial")
public class RefreshScopeRefreshedEvent extends ApplicationEvent {
public static final String DEFAULT_NAME = "__refreshAll__";
private String name;
public RefreshScopeRefreshedEvent() {
this(DEFAULT_NAME);
}
public RefreshScopeRefreshedEvent(String name) {
super(name);
this.name = name;
}
public String getName() {
return name;
}
}

61
src/main/java/org/springframework/cloud/context/scope/thread/ThreadLocalScopeCache.java

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
/*
* 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.scope.thread;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.cloud.context.scope.ScopeCache;
/**
* @author Dave Syer
*
*/
public class ThreadLocalScopeCache implements ScopeCache {
private ThreadLocal<ConcurrentMap<String, Object>> data = new ThreadLocal<ConcurrentMap<String, Object>>() {
protected ConcurrentMap<String, Object> initialValue() {
return new ConcurrentHashMap<String, Object>();
}
};
public Object remove(String name) {
return data.get().remove(name);
}
public Collection<Object> clear() {
ConcurrentMap<String, Object> map = data.get();
Collection<Object> values = new ArrayList<Object>(map.values());
map.clear();
return values;
}
public Object get(String name) {
return data.get().get(name);
}
public Object put(String name, Object value) {
Object result = data.get().putIfAbsent(name, value);
if (result!=null) {
return result;
}
return value;
}
}

36
src/main/java/org/springframework/cloud/context/scope/thread/ThreadScope.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2002-2009 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.scope.thread;
import org.springframework.cloud.context.scope.GenericScope;
/**
*
* @author Dave Syer
*
* @since 3.1
*
*/
public class ThreadScope extends GenericScope {
/**
* Create a scope instance and give it the default name: "thread".
*/
public ThreadScope() {
super();
super.setName("thread");
super.setScopeCache(new ThreadLocalScopeCache());
}
}

53
src/main/java/org/springframework/cloud/endpoint/GenericPostableMvcEndpoint.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* 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.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;
/**
* A convenient base class for MVC endpoints that accept a POST (instead of the default
* GET).
*
* @author Dave Syer
*
*/
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();
}
}

81
src/main/java/org/springframework/cloud/logging/LoggingRebinder.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
/*
* 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.logging;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
/**
* Listener that looks for {@link EnvironmentChangeEvent} and rebinds logger levels if any
* changed.
*
* @author Dave Syer
*
*/
public class LoggingRebinder implements ApplicationListener<EnvironmentChangeEvent>,
EnvironmentAware {
private final Log logger = LogFactory.getLog(getClass());
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (environment == null) {
return;
}
LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader());
setLogLevels(system, environment);
}
protected void setLogLevels(LoggingSystem system, Environment environment) {
Map<String, Object> levels = new RelaxedPropertyResolver(environment)
.getSubProperties("logging.level.");
for (Entry<String, Object> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
}
}
private void setLogLevel(LoggingSystem system, Environment environment, String name,
String level) {
try {
if (name.equalsIgnoreCase("root")) {
name = null;
}
level = environment.resolvePlaceholders(level);
system.setLogLevel(name, LogLevel.valueOf(level));
}
catch (RuntimeException ex) {
this.logger.error("Cannot set level: " + level + " for '" + name + "'");
}
}
}

15
src/main/resources/META-INF/spring.factories

@ -2,4 +2,17 @@ @@ -2,4 +2,17 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.context.restart.RestartListener
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration

35
src/test/java/org/springframework/cloud/bootstrap/BootstrapDisabledAutoConfigurationIntegrationTests.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package org.springframework.cloud.bootstrap;
import static org.junit.Assert.assertFalse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.bootstrap.BootstrapDisabledAutoConfigurationIntegrationTests.Application;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest("spring.cloud.bootstrap.enabled:false")
public class BootstrapDisabledAutoConfigurationIntegrationTests {
@Autowired
private ConfigurableEnvironment environment;
@Test
public void noBootstrapProperties() {
assertFalse(environment.getPropertySources().contains("bootstrap"));
}
@EnableAutoConfiguration
@Configuration
protected static class Application {
}
}

48
src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package org.springframework.cloud.bootstrap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.bootstrap.BootstrapOrderingAutoConfigurationIntegrationTests.Application;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest("encrypt.key:deadbeef")
@ActiveProfiles("encrypt")
public class BootstrapOrderingAutoConfigurationIntegrationTests {
@Autowired
private ConfigurableEnvironment environment;
@Test
public void bootstrapPropertiesExist() {
assertTrue(environment.getPropertySources().contains("bootstrap"));
}
@Test
public void normalPropertiesDecrypted() {
assertEquals("foo", environment.resolvePlaceholders("${foo}"));
}
@Test
public void bootstrapPropertiesDecrypted() {
assertEquals("bar", environment.resolvePlaceholders("${bar}"));
}
@EnableAutoConfiguration
@Configuration
protected static class Application {
}
}

323
src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java

@ -0,0 +1,323 @@ @@ -0,0 +1,323 @@
/*
* 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.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
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.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
/**
* @author Dave Syer
*
*/
public class BootstrapConfigurationTests {
private ConfigurableApplicationContext context;
@Rule
public ExpectedException expected = ExpectedException.none();
@After
public void close() {
// Expected.* is bound to the PropertySourceConfiguration below
System.clearProperty("expected.name");
System.clearProperty("expected.fail");
// Used to test system properties override
System.clearProperty("bootstrap.foo");
PropertySourceConfiguration.MAP.clear();
if (this.context != null) {
this.context.close();
}
}
@Test
public void pickupExternalBootstrapProperties() {
String externalPropertiesPath = getExternalProperties();
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class)
.properties("spring.cloud.bootstrap.location:" + externalPropertiesPath)
.run();
assertEquals("externalPropertiesInfoName", this.context.getEnvironment()
.getProperty("info.name"));
assertTrue(this.context.getEnvironment().getPropertySources()
.contains("bootstrap"));
}
/**
* Running the test from maven will start from a different directory then starting it
* from intellij
*
* @return
*/
private String getExternalProperties() {
String externalPropertiesPath = "";
File externalProperties = new File(
"src/test/resources/external-properties/bootstrap.properties");
if (externalProperties.exists()) {
externalPropertiesPath = externalProperties.getAbsolutePath();
}
else {
externalProperties = new File(
"spring-cloud-config-client/src/test/resources/external-properties/bootstrap.properties");
externalPropertiesPath = externalProperties.getAbsolutePath();
}
return externalPropertiesPath;
}
@Test
public void picksUpAdditionalPropertySource() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
System.setProperty("expected.name", "bootstrap");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
assertTrue(this.context.getEnvironment().getPropertySources()
.contains("bootstrap"));
}
@Test
public void failsOnPropertySource() {
System.setProperty("expected.fail", "true");
this.expected.expectMessage("Planned");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
}
@Test
public void overrideSystemPropertySourceByDefault() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
System.setProperty("bootstrap.foo", "system");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
}
@Test
public void systemPropertyOverrideFalse() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
PropertySourceConfiguration.MAP.put(
"spring.cloud.config.overrideSystemProperties", "false");
System.setProperty("bootstrap.foo", "system");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
assertEquals("system", this.context.getEnvironment().getProperty("bootstrap.foo"));
}
@Test
public void systemPropertyOverrideWhenOverrideDisallowed() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
PropertySourceConfiguration.MAP.put(
"spring.cloud.config.overrideSystemProperties", "false");
// If spring.cloud.config.allowOverride=false is in the remote property sources
// with sufficiently high priority it always wins. Admins can enforce it by adding
// their own remote property source.
PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "false");
System.setProperty("bootstrap.foo", "system");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
}
@Test
public void applicationNameInBootstrapAndMain() {
System.setProperty("expected.name", "main");
this.context = new SpringApplicationBuilder()
.web(false)
.properties("spring.cloud.bootstrap.name:other",
"spring.config.name:plain").sources(BareConfiguration.class)
.run();
assertEquals("app",
this.context.getEnvironment().getProperty("spring.application.name"));
// The parent is called "main" because spring.application.name is specified in
// other.properties (the bootstrap properties)
assertEquals(
"main",
this.context.getParent().getEnvironment()
.getProperty("spring.application.name"));
// The bootstrap context has a different "bootstrap" property source
assertNotSame(
this.context.getEnvironment().getPropertySources().get("bootstrap"),
((ConfigurableEnvironment) this.context.getParent().getEnvironment())
.getPropertySources().get("bootstrap"));
assertEquals("app", this.context.getId());
}
@Test
public void applicationNameNotInBootstrap() {
System.setProperty("expected.name", "main");
this.context = new SpringApplicationBuilder()
.web(false)
.properties("spring.cloud.bootstrap.name:application",
"spring.config.name:other").sources(BareConfiguration.class)
.run();
assertEquals("main",
this.context.getEnvironment().getProperty("spring.application.name"));
// The parent is called "application" because spring.application.name is not
// defined in the bootstrap properties
assertEquals("application", this.context.getParent().getEnvironment()
.getProperty("spring.application.name"));
}
@Test
public void applicationNameOnlyInBootstrap() {
System.setProperty("expected.name", "main");
this.context = new SpringApplicationBuilder().web(false)
.properties("spring.cloud.bootstrap.name:other")
.sources(BareConfiguration.class).run();
// The main context is called "main" because spring.application.name is specified
// in other.properties (and not in the main config file)
assertEquals("main",
this.context.getEnvironment().getProperty("spring.application.name"));
// The parent is called "main" because spring.application.name is specified in
// other.properties (the bootstrap properties this time)
assertEquals(
"main",
this.context.getParent().getEnvironment()
.getProperty("spring.application.name"));
assertEquals("main", this.context.getId());
}
@Test
public void environmentEnrichedOnceWhenSharedWithChildContext() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
this.context = new SpringApplicationBuilder().sources(BareConfiguration.class)
.environment(new StandardEnvironment()).child(BareConfiguration.class)
.web(false).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
assertEquals(this.context.getEnvironment(), this.context.getParent()
.getEnvironment());
MutablePropertySources sources = this.context.getEnvironment()
.getPropertySources();
PropertySource<?> bootstrap = sources.get("bootstrap");
assertNotNull(bootstrap);
assertEquals(0, sources.precedenceOf(bootstrap));
}
@Test
public void environmentEnrichedInParentContext() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
this.context = new SpringApplicationBuilder().sources(BareConfiguration.class)
.child(BareConfiguration.class).web(false).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
assertNotSame(this.context.getEnvironment(), this.context.getParent()
.getEnvironment());
assertTrue(this.context.getEnvironment().getPropertySources()
.contains("bootstrap"));
assertTrue(((ConfigurableEnvironment) this.context.getParent().getEnvironment())
.getPropertySources().contains("bootstrap"));
}
@Test
public void differentProfileInChild() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
// Profiles are always merged with the child
ConfigurableApplicationContext parent = new SpringApplicationBuilder()
.sources(BareConfiguration.class).profiles("parent").web(false).run();
this.context = new SpringApplicationBuilder(BareConfiguration.class)
.profiles("child").parent(parent).web(false).run();
assertNotSame(this.context.getEnvironment(), this.context.getParent()
.getEnvironment());
// The ApplicationContext merges profiles (profiles and property sources), see
// AbstractEnvironment.merge()
assertTrue(this.context.getEnvironment().acceptsProfiles("child", "parent"));
// But the parent is not a child
assertFalse(this.context.getParent().getEnvironment().acceptsProfiles("child"));
assertTrue(this.context.getParent().getEnvironment().acceptsProfiles("parent"));
assertTrue(((ConfigurableEnvironment) this.context.getParent().getEnvironment())
.getPropertySources().contains("bootstrap"));
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));
// The "bootstrap" property source is not shared now, but it has the same
// properties in it because they are pulled from the PropertySourceConfiguration
// below
assertEquals("bar",
this.context.getParent().getEnvironment().getProperty("bootstrap.foo"));
// The parent property source is there in the child because they are both in the
// "parent" profile (by virtue of the merge in AbstractEnvironment)
assertEquals("parent", this.context.getEnvironment().getProperty("info.name"));
}
@Configuration
@EnableConfigurationProperties
protected static class BareConfiguration {
}
@Configuration
@ConfigurationProperties("expected")
// This is added to bootstrap context as a source in bootstrap.properties
protected static class PropertySourceConfiguration implements PropertySourceLocator {
public static Map<String, Object> MAP = new HashMap<String, Object>(
Collections.<String, Object> singletonMap("bootstrap.foo", "bar"));
private String name;
private boolean fail = false;
@Override
public PropertySource<?> locate(Environment environment) {
if (this.name != null) {
assertEquals(this.name,
environment.getProperty("spring.application.name"));
}
if (this.fail) {
throw new RuntimeException("Planned");
}
return new MapPropertySource("testBootstrap", MAP);
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean isFail() {
return this.fail;
}
public void setFail(boolean fail) {
this.fail = fail;
}
}
}

24
src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionBootstrapConfigurationTests.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package org.springframework.cloud.bootstrap.encrypt;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.security.crypto.encrypt.TextEncryptor;
public class EncryptionBootstrapConfigurationTests {
@Test
public void rsaKeyStore() {
ConfigurableApplicationContext context = new SpringApplicationBuilder(
EncryptionBootstrapConfiguration.class).web(false).properties(
"encrypt.keyStore.location:classpath:/server.jks",
"encrypt.keyStore.password:letmein",
"encrypt.keyStore.alias:mytestkey", "encrypt.keyStore.secret:changeme")
.run();
TextEncryptor encryptor = context.getBean(TextEncryptor.class);
assertEquals("foo", encryptor.decrypt(encryptor.encrypt("foo")));
}
}

62
src/test/java/org/springframework/cloud/bootstrap/encrypt/EnvironmentDecryptApplicationListenerTests.java

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* 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 static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.security.crypto.encrypt.Encryptors;
/**
* @author Dave Syer
*
*/
public class EnvironmentDecryptApplicationListenerTests {
private EnvironmentDecryptApplicationInitializer listener = new EnvironmentDecryptApplicationInitializer(Encryptors.noOpText());
@Test
public void decryptCipherKey() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, "foo: {cipher}bar");
listener.initialize(context);
assertEquals("bar", context.getEnvironment().getProperty("foo"));
}
@Test(expected=IllegalStateException.class)
public void errorOnDecrypt() {
listener = new EnvironmentDecryptApplicationInitializer(Encryptors.text("deadbeef", "AFFE37"));
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, "foo: {cipher}bar");
listener.initialize(context);
assertEquals("bar", context.getEnvironment().getProperty("foo"));
}
@Test
public void errorOnDecryptWithEmpty() {
listener = new EnvironmentDecryptApplicationInitializer(Encryptors.text("deadbeef", "AFFE37"));
listener.setFailOnError(false);
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, "foo: {cipher}bar");
listener.initialize(context);
// Empty is safest fallback for undecryptable cipher
assertEquals("", context.getEnvironment().getProperty("foo"));
}
}

122
src/test/java/org/springframework/cloud/context/environment/EnvironmentManagerIntegrationTests.java

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.environment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import javax.servlet.ServletException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.context.environment.EnvironmentManagerIntegrationTests.TestConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class EnvironmentManagerIntegrationTests {
@Autowired
private TestProperties properties;
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void testRefresh() throws Exception {
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
this.mvc.perform(post("/env").param("message", "Foo")).andExpect(status().isOk()).andExpect(
content().string("{\"message\":\"Foo\"}"));
assertEquals("Foo", properties.getMessage());
}
@Test
public void testRefreshFails() throws Exception {
try {
this.mvc.perform(post("/env").param("delay", "foo")).andExpect(
status().is5xxServerError());
fail("expected ServletException");
} catch (ServletException e) {
// The underlying BindException is not handled by the dispatcher servlet
}
assertEquals(0, properties.getDelay());
}
public static void main(String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
@Bean
protected TestProperties properties() {
return new TestProperties();
}
}
@ConfigurationProperties
protected static class TestProperties {
private String message;
private int delay;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

114
src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.properties;
import static org.junit.Assert.assertEquals;
import javax.annotation.PostConstruct;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinderIntegrationTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ConfigurationPropertiesRebinderIntegrationTests {
@Autowired
private TestProperties properties;
@Autowired
private ConfigurationPropertiesRebinder rebinder;
@Autowired
private ConfigurableEnvironment environment;
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", properties.getMessage());
assertEquals(1, properties.getCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals(1, properties.getCount());
assertEquals("Hello scope!", properties.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
rebinder.rebind();
assertEquals("Foo", properties.getMessage());
assertEquals(2, properties.getCount());
}
@Configuration
@EnableConfigurationProperties
@Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
protected static class TestConfiguration {
@Bean
protected TestProperties properties() {
return new TestProperties();
}
}
@ConfigurationProperties
protected static class TestProperties {
private String message;
private int delay;
private int count = 0;
public int getCount() {
return count;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
@PostConstruct
public void init() {
this.count ++;
}
}
}

73
src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.restart;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
public class RestartIntegrationTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (context != null) {
context.close();
}
}
@Test
public void testRestartTwice() throws Exception {
context = SpringApplication.run(TestConfiguration.class, "--endpoints.restart.enabled=true", "--server.port=0");
RestartEndpoint endpoint = context.getBean(RestartEndpoint.class);
assertNotNull(context.getParent());
assertNull(context.getParent().getParent());
context = endpoint.restart();
assertNotNull(context);
assertNotNull(context.getParent());
assertNull(context.getParent().getParent());
RestartEndpoint next = context.getBean(RestartEndpoint.class);
assertNotSame(endpoint, next);
context = next.restart();
assertNotNull(context);
assertNotNull(context.getParent());
assertNull(context.getParent().getParent());
}
public static void main(String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
@Configuration
@EnableAutoConfiguration
protected static class TestConfiguration {
}
}

67
src/test/java/org/springframework/cloud/context/scope/refresh/ImportRefreshScopeIntegrationTests.java

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.ImportRefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ImportRefreshScopeIntegrationTests {
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Autowired
private ExampleService service;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Test
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertEquals("refresh", beanFactory.getBeanDefinition("scopedTarget.service").getScope());
assertEquals("Hello scope!", service.getMessage());
}
@Configuration("service")
@RefreshScope
public static class ExampleService {
public String getMessage() {
return "Hello scope!";
}
}
@Configuration
@Import({ RefreshAutoConfiguration.class, ExampleService.class })
protected static class TestConfiguration {
}
}

230
src/test/java/org/springframework/cloud/context/scope/refresh/MoreRefreshScopeIntegrationTests.java

@ -0,0 +1,230 @@ @@ -0,0 +1,230 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.MoreRefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MoreRefreshScopeIntegrationTests {
@Autowired
private TestService service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Autowired
private ConfigurableEnvironment environment;
@Before
public void init() {
TestService.reset();
}
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertTrue(service instanceof Advised);
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", service.getMessage());
assertEquals(1, TestService.getInitCount());
assertEquals(0, TestService.getDestroyCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, TestService.getInitCount());
assertEquals(1, TestService.getDestroyCount());
assertNotSame(id1, id2);
}
@Test
@DirtiesContext
public void testRefreshFails() throws Exception {
assertEquals("Hello scope!", service.getMessage());
// Change the dynamic property source...
EnvironmentTestUtils.addEnvironment(environment, "message:Foo", "delay:foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
try {
// If a refresh fails (e.g. a binding error in this case) the application is
// basically hosed.
assertEquals("Hello scope!", service.getMessage());
fail("expected BeanCreationException");
} catch (BeanCreationException e) {
}
// But we can fix it by fixing the binding error:
EnvironmentTestUtils.addEnvironment(environment, "delay:0");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
assertEquals("Foo", service.getMessage());
}
public static class TestService implements InitializingBean, DisposableBean {
private static Log logger = LogFactory.getLog(TestService.class);
private volatile static int initCount = 0;
private volatile static int destroyCount = 0;
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
initCount++;
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
destroyCount++;
message = null;
}
public static void reset() {
initCount = 0;
destroyCount = 0;
}
public static int getInitCount() {
return initCount;
}
public static int getDestroyCount() {
return destroyCount;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties
@Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static class TestConfiguration {
@Bean
@RefreshScope
protected TestProperties properties() {
return new TestProperties();
}
@Bean
@RefreshScope
public TestService service() {
TestService service = new TestService();
service.setMessage(properties().getMessage());
service.setDelay(properties().getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

114
src/test/java/org/springframework/cloud/context/scope/refresh/RefreshEndpointIntegrationTests.java

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
/*
* 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.scope.refresh;
import static org.junit.Assert.assertEquals;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshEndpointIntegrationTests.ClientApp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ClientApp.class)
@IntegrationTest("server.port:0")
@WebAppConfiguration
public class RefreshEndpointIntegrationTests {
@Value("${local.server.port}")
private int port;
@Test
public void webAccess() throws Exception {
TestRestTemplate template = new TestRestTemplate();
template.exchange(
getUrlEncodedEntity("http://localhost:" + port + "/env", "message",
"Hello Dave!"), String.class);
template.postForObject("http://localhost:" + port + "/refresh", "", String.class);
String message = template.getForObject("http://localhost:" + port + "/",
String.class);
assertEquals("Hello Dave!", message);
}
private RequestEntity<?> getUrlEncodedEntity(String uri, String key, String value)
throws URISyntaxException {
MultiValueMap<String, String> env = new LinkedMultiValueMap<String, String>(
Collections.singletonMap("message", Arrays.asList("Hello Dave!")));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
RequestEntity<MultiValueMap<String, String>> entity = new RequestEntity<MultiValueMap<String, String>>(
env, headers, HttpMethod.POST, new URI(uri));
return entity;
}
@Configuration
@EnableAutoConfiguration
protected static class ClientApp {
@Bean
@RefreshScope
public Controller controller() {
return new Controller();
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
@RestController
protected static class Controller {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
}
}

185
src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeConcurrencyTests.java

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.scope.refresh;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeConcurrencyTests.TestConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringApplicationConfiguration(classes=TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class RefreshScopeConcurrencyTests {
private static Log logger = LogFactory.getLog(RefreshScopeConcurrencyTests.class);
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Autowired
private Service service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Test
@Repeat(10)
@DirtiesContext
public void testConcurrentRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
properties.setMessage("Foo");
properties.setDelay(500);
final CountDownLatch latch = new CountDownLatch(1);
Future<String> result = executor.submit(new Callable<String>() {
public String call() throws Exception {
logger.debug("Background started.");
try {
return service.getMessage();
} finally {
latch.countDown();
logger.debug("Background done.");
}
}
});
assertTrue(latch.await(1500, TimeUnit.MILLISECONDS));
logger.info("Refreshing");
scope.refreshAll();
assertEquals("Foo", service.getMessage());
/*
* This is the most important assertion: we don't want a null value because that means the bean was destroyed
* and not re-initialized before we accessed it.
*/
assertNotNull(result.get());
assertEquals("Hello scope!", result.get());
}
public static interface Service {
String getMessage();
}
public static class ExampleService implements Service, InitializingBean, DisposableBean {
private static Log logger = LogFactory.getLog(ExampleService.class);
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
message = null;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
}
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@Import({RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
protected static class TestConfiguration {
@Autowired
private TestProperties properties;
@Bean
@RefreshScope
public ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage(properties.getMessage());
service.setDelay(properties.getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

169
src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeConfigurationTests.java

@ -0,0 +1,169 @@ @@ -0,0 +1,169 @@
/*
* 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.scope.refresh;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.scope.refresh.RefreshScopeConfigurationTests.NestedApp.NestedController;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
*
*/
public class RefreshScopeConfigurationTests {
private AnnotationConfigApplicationContext context;
@Rule
public ExpectedException expected = ExpectedException.none();
@After
public void init() {
if (context!=null) {
context.close();
}
}
private void refresh() {
EnvironmentManager environmentManager = context.getBean(EnvironmentManager.class);
environmentManager.setProperty("message", "Hello Dave!");
org.springframework.cloud.context.scope.refresh.RefreshScope scope = context.getBean(org.springframework.cloud.context.scope.refresh.RefreshScope.class);
scope.refreshAll();
}
/**
* See gh-43
*/
@Test
public void configurationWithRefreshScope() throws Exception {
context = new AnnotationConfigApplicationContext(Application.class,
PropertyPlaceholderAutoConfiguration.class, RefreshAutoConfiguration.class);
Application application = context.getBean(Application.class);
assertEquals("refresh", context.getBeanDefinition("scopedTarget.application").getScope());
application.hello();
refresh();
String message = application.hello();
assertEquals("Hello Dave!", message);
}
@Test
public void refreshScopeOnBean() throws Exception {
context = new AnnotationConfigApplicationContext(ClientApp.class,
PropertyPlaceholderAutoConfiguration.class, RefreshAutoConfiguration.class);
Controller application = context.getBean(Controller.class);
application.hello();
refresh();
String message = application.hello();
assertEquals("Hello Dave!", message);
}
@Test
public void refreshScopeOnNested() throws Exception {
context = new AnnotationConfigApplicationContext(NestedApp.class,
PropertyPlaceholderAutoConfiguration.class, RefreshAutoConfiguration.class);
NestedController application = context.getBean(NestedController.class);
application.hello();
refresh();
String message = application.hello();
assertEquals("Hello Dave!", message);
}
// WTF? Maven can't compile without the FQN on this one (not the others).
@org.springframework.context.annotation.Configuration
protected static class NestedApp {
@RestController
@RefreshScope
protected static class NestedController {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
@Configuration("application")
@RefreshScope
protected static class Application {
@Value("${message:Hello World!}")
String message = "Hello World";
@RequestMapping("/")
public String hello() {
return message;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Configuration
protected static class ClientApp {
@Bean
@RefreshScope
public Controller controller() {
return new Controller();
}
public static void main(String[] args) {
SpringApplication.run(ClientApp.class, args);
}
}
@RestController
protected static class Controller {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
}
}

229
src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeIntegrationTests.java

@ -0,0 +1,229 @@ @@ -0,0 +1,229 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.scope.refresh;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.GenericScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeIntegrationTests.TestConfiguration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class RefreshScopeIntegrationTests {
@Autowired
private Service service;
@Autowired
private TestProperties properties;
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Before
public void init() {
ExampleService.reset();
}
@Test
@DirtiesContext
public void testSimpleProperties() throws Exception {
assertEquals("Hello scope!", service.getMessage());
assertTrue(service instanceof Advised);
// Change the dynamic property source...
properties.setMessage("Foo");
// ...but don't refresh, so the bean stays the same:
assertEquals("Hello scope!", service.getMessage());
assertEquals(1, ExampleService.getInitCount());
assertEquals(0, ExampleService.getDestroyCount());
}
@Test
@DirtiesContext
public void testRefresh() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
properties.setMessage("Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refreshAll();
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, ExampleService.getInitCount());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
assertNotNull(ExampleService.event);
assertEquals(RefreshScopeRefreshedEvent.DEFAULT_NAME,
ExampleService.event.getName());
}
@Test
@DirtiesContext
public void testRefreshBean() throws Exception {
assertEquals("Hello scope!", service.getMessage());
String id1 = service.toString();
// Change the dynamic property source...
properties.setMessage("Foo");
// ...and then refresh, so the bean is re-initialized:
scope.refresh("service");
String id2 = service.toString();
assertEquals("Foo", service.getMessage());
assertEquals(2, ExampleService.getInitCount());
assertEquals(1, ExampleService.getDestroyCount());
assertNotSame(id1, id2);
assertNotNull(ExampleService.event);
assertEquals(GenericScope.SCOPED_TARGET_PREFIX + "service",
ExampleService.event.getName());
}
public static interface Service {
String getMessage();
}
public static class ExampleService implements Service, InitializingBean,
DisposableBean, ApplicationListener<RefreshScopeRefreshedEvent> {
private static Log logger = LogFactory.getLog(ExampleService.class);
private volatile static int initCount = 0;
private volatile static int destroyCount = 0;
private volatile static RefreshScopeRefreshedEvent event;
private String message = null;
private volatile long delay = 0;
public void setDelay(long delay) {
this.delay = delay;
}
public void afterPropertiesSet() throws Exception {
logger.debug("Initializing message: " + message);
initCount++;
}
public void destroy() throws Exception {
logger.debug("Destroying message: " + message);
destroyCount++;
message = null;
}
public static void reset() {
initCount = 0;
destroyCount = 0;
event = null;
}
public static int getInitCount() {
return initCount;
}
public static int getDestroyCount() {
return destroyCount;
}
public void setMessage(String message) {
logger.debug("Setting message: " + message);
this.message = message;
}
public String getMessage() {
logger.debug("Getting message: " + message);
try {
Thread.sleep(delay);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Returning message: " + message);
return message;
}
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent e) {
event = e;
}
}
@Configuration
@EnableConfigurationProperties(TestProperties.class)
@Import({ RefreshAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static class TestConfiguration {
@Autowired
private TestProperties properties;
@Bean
@RefreshScope
public ExampleService service() {
ExampleService service = new ExampleService();
service.setMessage(properties.getMessage());
service.setDelay(properties.getDelay());
return service;
}
}
@ConfigurationProperties
@ManagedResource
protected static class TestProperties {
private String message;
private int delay;
@ManagedAttribute
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@ManagedAttribute
public int getDelay() {
return delay;
}
public void setDelay(int delay) {
this.delay = delay;
}
}
}

100
src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeWebIntegrationTests.java

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
/*
* 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.scope.refresh;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.context.scope.refresh.RefreshScopeWebIntegrationTests.Application;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dave Syer
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class RefreshScopeWebIntegrationTests {
@Autowired
private org.springframework.cloud.context.scope.refresh.RefreshScope scope;
@Autowired
private EnvironmentManager environmentManager;
@Autowired
private Client application;
@Autowired
private ConfigurableListableBeanFactory beanFactory;
@Test
public void scopeOnBeanDefinition() throws Exception {
assertEquals("refresh", beanFactory.getBeanDefinition("scopedTarget.application").getScope());
}
@Test
public void beanAccess() throws Exception {
application.hello();
environmentManager.setProperty("message", "Hello Dave!");
scope.refreshAll();
String message = application.hello();
assertEquals("Hello Dave!", message);
}
@Configuration
@EnableAutoConfiguration
protected static class Application {
@Bean
@RefreshScope
public Client application() {
return new Client();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
protected static class Client {
@Value("${message:Hello World!}")
String message;
@RequestMapping("/")
public String hello() {
return message;
}
}
}

57
src/test/java/org/springframework/cloud/logging/LoggingRebinderTests.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* 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.logging;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import org.junit.After;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
/**
* @author Dave Syer
*
*/
public class LoggingRebinderTests {
private LoggingRebinder rebinder = new LoggingRebinder();
private Logger logger = LoggerFactory.getLogger("org.springframework.web");
@After
public void reset() {
LoggingSystem.get(getClass().getClassLoader()).setLogLevel("org.springframework.web", LogLevel.INFO);
}
@Test
public void logLevelsChanged() {
assertFalse(logger.isTraceEnabled());
StandardEnvironment environment = new StandardEnvironment();
EnvironmentTestUtils.addEnvironment(environment, "logging.level.org.springframework.web=TRACE");
rebinder.setEnvironment(environment);
rebinder.onApplicationEvent(new EnvironmentChangeEvent(Collections.singleton("logging.level.org.springframework.web")));
assertTrue(logger.isTraceEnabled());
}
}

1
src/test/resources/application-encrypt.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
foo: {cipher}e4e061f9fe39ba5b14d8012d2f17d39775606039409b71ed4be0fdd033d5324a

5
src/test/resources/application.properties

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
message: Hello scope!
delay: 0
debug: true
#logging.level.org.springframework.web: DEBUG
#logging.level.org.springframework.context.annotation: DEBUG

1
src/test/resources/bootstrap-encrypt.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
bar: {cipher}6154ca04d4bb6144d672c4e3d750b5147116dd381946d51fa44f8bc25dc256f4

1
src/test/resources/bootstrap-parent.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
info.name: parent

2
src/test/resources/bootstrap.properties

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
spring.main.sources: org.springframework.cloud.bootstrap.config.BootstrapConfigurationTests.PropertySourceConfiguration
info.name: child

1
src/test/resources/external-properties/bootstrap.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
info.name: externalPropertiesInfoName

1
src/test/resources/other.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
spring.application.name: main

1
src/test/resources/plain.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
spring.application.name: app

BIN
src/test/resources/server.jks

Binary file not shown.
Loading…
Cancel
Save