Browse Source
Refresh, bootstrap, encryption all moved in to the common jar, for now at leastpull/15/head
Dave Syer
10 years ago
59 changed files with 5155 additions and 1 deletions
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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 {}; |
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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); |
||||
|
||||
} |
@ -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 { |
||||
|
||||
} |
||||
|
||||
} |
@ -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?"); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
@ -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 { |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -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()); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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); |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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><aop:auto-proxy/></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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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()); |
||||
} |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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 + "'"); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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 { |
||||
|
||||
} |
||||
|
||||
} |
@ -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 { |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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"))); |
||||
} |
||||
|
||||
} |
@ -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")); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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 ++; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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 { |
||||
} |
||||
|
||||
} |
@ -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 { |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
foo: {cipher}e4e061f9fe39ba5b14d8012d2f17d39775606039409b71ed4be0fdd033d5324a |
@ -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 |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
bar: {cipher}6154ca04d4bb6144d672c4e3d750b5147116dd381946d51fa44f8bc25dc256f4 |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
info.name: parent |
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
spring.main.sources: org.springframework.cloud.bootstrap.config.BootstrapConfigurationTests.PropertySourceConfiguration |
||||
info.name: child |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
info.name: externalPropertiesInfoName |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
spring.application.name: main |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
spring.application.name: app |
Binary file not shown.
Loading…
Reference in new issue