Commit f0bfecd3 authored by Phillip Webb's avatar Phillip Webb

Refactor PropertySource support

Locate PropertySourcesLoaders using SpringFactoriesLoader and refactor
the interface to expose file extensions and support 'profiles' within
documents.

Rework ConfigFileApplicationListener for consistent profile loading.
Profiles are now loaded in a consistent order for both profile specific
files, and contained profile documents (i.e. YAML sub-sections).

Also update ConfigFileApplicationListener so that it no longer directly
processes @ProperySource annotations. Instead the standard Spring
ConfigurationClassPostProcessor will insert @PropertySource items with
ConfigFileApplicationListener later re-ordering them.

The SpringApplication can no longer be configured using @ProperySource
annotations, however, application.properties may still be used.

Fixes gh-322
parent 06494e06
...@@ -98,7 +98,7 @@ public class VcapApplicationListener implements ...@@ -98,7 +98,7 @@ public class VcapApplicationListener implements
private static final String VCAP_SERVICES = "VCAP_SERVICES"; private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileApplicationListener so values there can use these ones // Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileApplicationListener.DEFAULT_CONFIG_LISTENER_ORDER - 1;; private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;;
private final JsonParser parser = JsonParserFactory.getJsonParser(); private final JsonParser parser = JsonParserFactory.getJsonParser();
......
...@@ -16,40 +16,41 @@ ...@@ -16,40 +16,41 @@
package org.springframework.boot.config; package org.springframework.boot.config;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Queue;
import java.util.Random;
import java.util.Set; import java.util.Set;
import org.springframework.beans.PropertyValues; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.PropertySourcesPropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.PropertySources; import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.util.DigestUtils; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -63,441 +64,338 @@ import org.springframework.util.StringUtils; ...@@ -63,441 +64,338 @@ import org.springframework.util.StringUtils;
* <li>file:./config/:</li> * <li>file:./config/:</li>
* </ul> * </ul>
* <p> * <p>
* Alternative locations and names can be specified using * Alternative search locations and names can be specified using
* {@link #setSearchLocations(String[])} and {@link #setNames(String)}. * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}.
* <p> * <p>
* Additional files will also be loaded based on active profiles. For example if a 'web' * Additional files will also be loaded based on active profiles. For example if a 'web'
* profile is active 'application-web.properties' and 'application-web.yml' will be * profile is active 'application-web.properties' and 'application-web.yml' will be
* considered. * considered.
* <p> * <p>
* The 'spring.config.name' property can be used to specify an alternative name to load or * The 'spring.config.name' property can be used to specify an alternative name to load
* alternatively the 'spring.config.location' property can be used to specify an exact * and the 'spring.config.location' property can be used to specify alternative search
* resource location. * locations or specific files.
* <p>
* Configuration properties are also bound to the {@link SpringApplication}. This makes it
* possible to set {@link SpringApplication} properties dynamically, like the sources
* ("spring.main.sources" - a CSV list) the flag to indicate a web environment
* ("spring.main.web_environment=true") or the flag to switch off the banner
* ("spring.main.show_banner=false").
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ConfigFileApplicationListener implements public class ConfigFileApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered { ApplicationListener<ApplicationEvent>, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
private static final String LOCATION_VARIABLE = "${spring.config.location}"; private static final String CONFIG_NAME_PROPERTY = "spring.config.name";
public static final int DEFAULT_CONFIG_LISTENER_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; private static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
private String[] searchLocations = new String[] { "classpath:/", "file:./", private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,file:./,"
"classpath:/config/", "file:./config/" }; + "classpath:/config/,file:./config/";
private String names = "${spring.config.name:application}"; private static final String DEFAULT_NAMES = "application";
private int order = DEFAULT_CONFIG_LISTENER_ORDER; public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private final ConversionService conversionService = new DefaultConversionService(); private String searchLocations;
private final Map<String, PropertySource<?>> cache = new HashMap<String, PropertySource<?>>(); private String names;
private final PropertySourceAnnotations annotations = new PropertySourceAnnotations(); private int order = DEFAULT_ORDER;
private PropertySourceLoadersFactory propertySourceLoadersFactory = new DefaultPropertySourceLoadersFactory(); private final ConversionService conversionService = new DefaultConversionService();
/**
* Binds the early {@link Environment} to the {@link SpringApplication}. This makes it
* possible to set {@link SpringApplication} properties dynamically, like the sources
* ("spring.main.sources" - a CSV list) the flag to indicate a web environment
* ("spring.main.web_environment=true") or the flag to switch off the banner
* ("spring.main.show_banner=false").
*/
@Override @Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
};
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
Environment environment = event.getEnvironment(); Environment environment = event.getEnvironment();
if (environment instanceof ConfigurableEnvironment) { if (environment instanceof ConfigurableEnvironment) {
configure((ConfigurableEnvironment) environment, event.getSpringApplication()); onApplicationEnvironmentPreparedEvent((ConfigurableEnvironment) environment,
event.getSpringApplication());
} }
} }
private void configure(ConfigurableEnvironment environment, private void onApplicationEnvironmentPreparedEvent(
SpringApplication springApplication) { ConfigurableEnvironment environment, SpringApplication application) {
for (Object source : springApplication.getSources()) { RandomValuePropertySource.addToEnvironment(environment);
this.annotations.addFromSource(source); try {
PropertySource<?> defaultProperties = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
new Loader(environment).load();
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
} }
load(environment, new DefaultResourceLoader());
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource("random"));
int sourcesSizeBefore = springApplication.getSources().size();
// Set bean properties from the early environment
PropertyValues propertyValues = new PropertySourcesPropertyValues(
environment.getPropertySources());
RelaxedDataBinder binder = new RelaxedDataBinder(springApplication, "spring.main");
binder.setConversionService(this.conversionService);
binder.bind(propertyValues);
if (springApplication.getSources().size() > sourcesSizeBefore) {
// Configure again in case there are new @PropertySources
configure(environment, springApplication);
} }
catch (IOException ex) {
throw new IllegalStateException("Unable to load configuration files", ex);
} }
bindToSpringApplication(application, environment);
private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
LoadCandidates candidates = new LoadCandidates(environment, resourceLoader);
PropertySource<?> defaultProperties = environment.getPropertySources().remove(
"defaultProperties");
// Load to allow a file that defines active profiles to be considered
String firstPropertySourceName = loadInitial(environment, resourceLoader,
candidates);
// Apply the active profiles (if any) from the first property source
if (environment.containsProperty(ACTIVE_PROFILES_PROPERTY)) {
activeProfilesFromProperty(environment,
environment.getProperty(ACTIVE_PROFILES_PROPERTY), true);
} }
// Apply any profile additions from any source private void bindToSpringApplication(SpringApplication application,
activeProfileAdditionsFromAnySource(environment); ConfigurableEnvironment environment) {
RelaxedDataBinder binder = new RelaxedDataBinder(application, "spring.main");
// Repeatedly load property sources in case additional profiles are activated binder.setConversionService(this.conversionService);
int numberOfPropertySources; binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
do {
numberOfPropertySources = environment.getPropertySources().size();
activeProfileAdditionsFromAnySource(environment);
loadAgain(environment, resourceLoader, candidates, firstPropertySourceName);
} }
while (environment.getPropertySources().size() > numberOfPropertySources);
if (defaultProperties != null) { private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
environment.getPropertySources().addLast(defaultProperties); ConfigurableApplicationContext context = event.getApplicationContext();
} context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor(
context));
} }
private void activeProfileAdditionsFromAnySource(ConfigurableEnvironment environment) { public void setOrder(int order) {
for (PropertySource<?> propertySource : environment.getPropertySources()) { this.order = order;
if (propertySource.containsProperty(ACTIVE_PROFILES_PROPERTY)) {
activeProfilesFromProperty(environment,
propertySource.getProperty(ACTIVE_PROFILES_PROPERTY), false);
}
}
} }
private void activeProfilesFromProperty(ConfigurableEnvironment environment, @Override
Object property, boolean addAll) { public int getOrder() {
for (String profile : StringUtils.commaDelimitedListToSet(property.toString())) { return this.order;
boolean addition = profile.startsWith("+");
profile = (addition ? profile.substring(1) : profile);
if (addAll || addition) {
environment.addActiveProfile(profile);
} }
/**
* Set the search locations that will be considered as a comma-separated list.
*/
public void setSearchLocations(String locations) {
Assert.hasLength(locations, "Locations must not be empty");
this.searchLocations = locations;
} }
/**
* Sets the names of the files that should be loaded (excluding file extension) as a
* comma-separated list.
*/
public void setSearchNames(String names) {
Assert.hasLength(names, "Names must not be empty");
this.names = names;
} }
private String loadInitial(ConfigurableEnvironment environment, /**
ResourceLoader resourceLoader, LoadCandidates candidates) { * {@link BeanFactoryPostProcessor} to re-order our property sources below any
String firstSourceName = null; * {@code @ProperySource} items added by the {@link ConfigurationClassPostProcessor}.
// Initial load allows profiles to be activated */
for (String candidate : candidates) { private class PropertySourceOrderingPostProcessor implements
for (String path : StringUtils.commaDelimitedListToStringArray(environment BeanFactoryPostProcessor, Ordered {
.resolvePlaceholders(candidate))) {
if (LOCATION_VARIABLE.equals(candidate) && !path.contains("$")) { private ConfigurableApplicationContext context;
if (!path.contains(":")) {
path = "file:" + path;
}
path = StringUtils.cleanPath(path);
}
PropertySource<?> source = loadPropertySource(environment, public PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) {
resourceLoader, path, null); this.context = context;
if (source != null) {
if (firstSourceName == null) {
firstSourceName = source.getName();
}
environment.getPropertySources().addLast(source);
}
}
}
return firstSourceName;
} }
private void loadAgain(ConfigurableEnvironment environment, @Override
ResourceLoader resourceLoader, LoadCandidates candidates, public int getOrder() {
String firstPropertySourceName) { return Ordered.HIGHEST_PRECEDENCE;
for (String profile : environment.getActiveProfiles()) {
for (String candidate : candidates) {
PropertySource<?> source = loadPropertySource(environment,
resourceLoader, candidate, profile);
addBeforeOrLast(environment, firstPropertySourceName, source);
}
}
} }
private void addBeforeOrLast(ConfigurableEnvironment environment, @Override
String relativePropertySourceName, PropertySource<?> source) { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
if (source != null) { throws BeansException {
MutablePropertySources propertySources = environment.getPropertySources(); reorderSources(this.context.getEnvironment());
// Originals go at the end so they don't override the specific profiles
if (relativePropertySourceName != null) {
propertySources.addBefore(relativePropertySourceName, source);
} }
else {
propertySources.addLast(source); private void reorderSources(ConfigurableEnvironment environment) {
ConfigurationPropertySources.finishAndRelocate(environment
.getPropertySources());
PropertySource<?> defaultProperties = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
} }
} }
} }
private PropertySource<?> loadPropertySource(ConfigurableEnvironment environment, /**
ResourceLoader resourceLoader, String location, String profile) { * Loads candidate property sources and configures the active profiles.
*/
private class Loader {
Class<?> type = this.annotations.configuration(location); private final ConfigurableEnvironment environment;
String suffix = "." + StringUtils.getFilenameExtension(location); private final ResourceLoader resourceLoader = new DefaultResourceLoader();
if (StringUtils.hasLength(profile)) {
location = location.replace(suffix, "-" + profile + suffix);
}
if (isPropertySourceAnnotationOnExcludedType(environment, profile, type, location)) { private PropertySourcesLoader propertiesLoader;
return null;
} private Queue<String> profiles;
private boolean activatedProfiles;
Resource resource = resourceLoader.getResource(location); public Loader(ConfigurableEnvironment environment) {
String name = this.annotations.name(location); this.environment = environment;
name = (name != null ? name : location);
return getPropertySource(environment, name, resource, profile);
} }
private boolean isPropertySourceAnnotationOnExcludedType(Environment environment, public void load() throws IOException {
String profile, Class<?> type, String location) { this.propertiesLoader = new PropertySourcesLoader();
this.profiles = new LinkedList<String>();
this.profiles.add(null);
this.profiles.addAll(Arrays.asList(this.environment.getActiveProfiles()));
this.activatedProfiles = false;
addActiveProfiles(this.environment.getProperty(ACTIVE_PROFILES_PROPERTY));
if (type == null) { while (!this.profiles.isEmpty()) {
// No configuration class to worry about, just a vanilla properties location String profile = this.profiles.poll();
return false; for (String location : getSearchLocations()) {
for (String name : getSearchNames()) {
load(location, name, profile);
}
}
} }
if (StringUtils.hasText(profile) addConfigurationProperties(this.propertiesLoader.getPropertySources());
&& !this.annotations.getLocations().contains(location)) {
// We are looking for profile specific properties and this one isn't
// explicitly asked for in propertySourceAnnotations
return true;
} }
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader( private void load(String location, String name, String profile)
new DefaultListableBeanFactory(), environment); throws IOException {
int before = reader.getRegistry().getBeanDefinitionCount();
reader.register(type);
int after = reader.getRegistry().getBeanDefinitionCount();
// Return if the configuration class was @Conditional and excluded // Try to load directly from the location
return (after == before); PropertySource<?> locationPropertySource = load(location, profile);
}
private PropertySource<?> getPropertySource(Environment environment, String name, // If that fails, try a search
Resource resource, String profile) { if (locationPropertySource == null) {
if (resource == null || !resource.exists()) { for (String ext : this.propertiesLoader.getAllFileExtensions()) {
return null; if (profile != null) {
// Try the profile specific file (with a null profile section)
load(location + name + "-" + profile + "." + ext, null);
} }
String key = resource.getDescription() + (profile == null ? "" : "#" + profile); // Try the profile (if any) specific section of the normal file
if (this.cache.containsKey(key)) { load(location + name + "." + ext, profile);
return this.cache.get(key);
}
for (PropertySourceLoader loader : this.propertySourceLoadersFactory
.getLoaders(environment)) {
if (loader.supports(resource)) {
PropertySource<?> propertySource = loader.load(name, resource);
this.cache.put(key, propertySource);
return propertySource;
} }
} }
throw new IllegalStateException("No supported loader found for "
+ "configuration resource: " + resource);
} }
public void setOrder(int order) { private PropertySource<?> load(String resourceLocation, String profile)
this.order = order; throws IOException {
} Resource resource = this.resourceLoader.getResource(resourceLocation);
if (resource != null && resource.exists()) {
@Override String name = "applicationConfig: " + resource.getDescription();
public int getOrder() { if (StringUtils.hasLength(profile)) {
return this.order; name += " " + profile;
} }
PropertySource<?> propertySource = this.propertiesLoader.load(resource,
/** name, profile);
* Sets the names of the files that should be loaded (excluding file extension) as a if (propertySource != null) {
* comma separated list. Defaults to "application". addActiveProfiles(propertySource
*/ .getProperty(ACTIVE_PROFILES_PROPERTY));
public void setNames(String names) {
this.names = names;
} }
return propertySource;
/**
* Set the search locations that will be considered.
*/
public void setSearchLocations(String[] searchLocations) {
this.searchLocations = (searchLocations == null ? null : searchLocations.clone());
} }
return null;
/**
* Set the {@link PropertySourceLoadersFactory} that will be used to create
* {@link PropertySourceLoader}s.
*/
public void setPropertySourceLoadersFactory(
PropertySourceLoadersFactory propertySourceLoaderFactory) {
this.propertySourceLoadersFactory = propertySourceLoaderFactory;
} }
/** private void addActiveProfiles(Object property) {
* Provides {@link Iterable} access to candidate property sources. String profiles = (property == null ? null : property.toString());
*/ boolean profilesNotActivatedWhenCalled = !this.activatedProfiles;
private class LoadCandidates implements Iterable<String> { for (String profile : asResolvedSet(profiles, null)) {
boolean addition = profile.startsWith("+");
private final List<String> candidates; profile = (addition ? profile.substring(1) : profile);
if (profilesNotActivatedWhenCalled || addition) {
public LoadCandidates(ConfigurableEnvironment environment, this.profiles.add(profile);
ResourceLoader resourceLoader) { this.environment.addActiveProfile(profile);
Set<String> candidates = new LinkedHashSet<String>(); this.activatedProfiles = true;
addLoadCandidatesFromSearchLocations(environment, candidates);
candidates.add(LOCATION_VARIABLE);
// @PropertySource annotation locations go last here (eventually highest
// priority). This unfortunately isn't the same semantics as @PropertySource
// in Spring and it's hard to change that (so the property source gets added
// again in last position by Spring later in the cycle).
addLoadCandidatesFromAnnotations(environment, resourceLoader, candidates);
this.candidates = new ArrayList<String>(candidates);
Collections.reverse(this.candidates);
} }
private void addLoadCandidatesFromSearchLocations(
ConfigurableEnvironment environment, Set<String> candidates) {
String[] names = StringUtils.commaDelimitedListToStringArray(environment
.resolvePlaceholders(ConfigFileApplicationListener.this.names));
for (String location : ConfigFileApplicationListener.this.searchLocations) {
for (String extension : new String[] { ".properties", ".yml" }) {
for (int i = names.length - 1; i >= 0; i--) {
candidates.add(location + names[i] + extension);
} }
} }
public Set<String> getSearchLocations() {
Set<String> locations = new LinkedHashSet<String>();
locations.addAll(asResolvedSet(
ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
for (String path : asResolvedSet(
this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
if (!path.contains("$")) {
if (!path.contains(":")) {
path = "file:" + path;
} }
path = StringUtils.cleanPath(path);
} }
locations.add(path);
private void addLoadCandidatesFromAnnotations(
ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Set<String> candidates) {
for (String location : ConfigFileApplicationListener.this.annotations
.getLocations()) {
Resource resource = resourceLoader.getResource(environment
.resolvePlaceholders(location));
if (!ConfigFileApplicationListener.this.annotations
.ignoreResourceNotFound(location) && !resource.exists()) {
throw new IllegalStateException("Resource not found: " + location);
} }
candidates.add(location);
} }
return locations;
} }
@Override public Set<String> getSearchNames() {
public Iterator<String> iterator() { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return this.candidates.iterator(); return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
} }
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
} }
/** private Set<String> asResolvedSet(String value, String fallback) {
* {@link PropertySource} that returns a random value for any property that starts List<String> list = Arrays.asList(StringUtils
* with {@literal "random."}. Return a {@code byte[]} unless the property name ends .commaDelimitedListToStringArray(value != null ? this.environment
* with {@literal ".int} or {@literal ".long"}. .resolvePlaceholders(value) : fallback));
*/ Collections.reverse(list);
private static class RandomValuePropertySource extends PropertySource<Random> { return new LinkedHashSet<String>(list);
public RandomValuePropertySource(String name) {
super(name, new Random());
} }
@Override private void addConfigurationProperties(MutablePropertySources sources) {
public Object getProperty(String name) { List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
if (!name.startsWith("random.")) { for (PropertySource<?> item : sources) {
return null; reorderedSources.add(item);
}
if (name.endsWith("int")) {
return getSource().nextInt();
} }
if (name.endsWith("long")) { Collections.reverse(reorderedSources);
return getSource().nextLong(); this.environment.getPropertySources().addLast(
} new ConfigurationPropertySources(reorderedSources));
byte[] bytes = new byte[32];
getSource().nextBytes(bytes);
return DigestUtils.md5DigestAsHex(bytes);
} }
} }
/** /**
* Holds details collected from * Holds the configuration {@link PropertySource}s as they are loaded can relocate
* {@link org.springframework.context.annotation.PropertySource} annotations. * them once configuration classes have been processed.
*/ */
private static class PropertySourceAnnotations { static class ConfigurationPropertySources extends PropertySource<Object> {
private final Collection<String> locations = new LinkedHashSet<String>();
private final Map<String, String> names = new HashMap<String, String>();
private final Map<String, Class<?>> configs = new HashMap<String, Class<?>>(); private static final String NAME = "applicationConfigurationProperties";
private final Map<String, Boolean> ignores = new HashMap<String, Boolean>(); private final Collection<PropertySource<?>> sources;
public void addFromSource(Object source) { public ConfigurationPropertySources(Collection<PropertySource<?>> sources) {
if (source instanceof Class<?>) { super(NAME);
addFromSource((Class<?>) source); this.sources = sources;
}
} }
private void addFromSource(Class<?> source) { @Override
for (org.springframework.context.annotation.PropertySource propertySource : AnnotationUtils public Object getProperty(String name) {
.getRepeatableAnnotation(source, PropertySources.class, for (PropertySource<?> propertySource : this.sources) {
org.springframework.context.annotation.PropertySource.class)) { Object value = propertySource.getProperty(name);
add(source, propertySource); if (value != null) {
} return value;
}
private void add(Class<?> source,
org.springframework.context.annotation.PropertySource annotation) {
this.locations.addAll(Arrays.asList(annotation.value()));
if (StringUtils.hasText(annotation.name())) {
for (String location : annotation.value()) {
this.names.put(location, annotation.name());
}
}
for (String location : annotation.value()) {
boolean reallyIgnore = annotation.ignoreResourceNotFound();
if (this.ignores.containsKey(location)) {
// Only if they all ignore this location will it be ignored
reallyIgnore &= this.ignores.get(location);
}
this.ignores.put(location, reallyIgnore);
this.configs.put(location, source);
} }
} }
return null;
public Class<?> configuration(String location) {
return this.configs.get(location);
} }
public boolean ignoreResourceNotFound(String location) { public static void finishAndRelocate(MutablePropertySources propertySources) {
return Boolean.TRUE.equals(this.ignores.get(location)); ConfigurationPropertySources removed = (ConfigurationPropertySources) propertySources
.remove(ConfigurationPropertySources.NAME);
if (removed != null) {
for (PropertySource<?> propertySource : removed.sources) {
propertySources.addLast(propertySource);
} }
public String name(String location) {
String name = this.names.get(location);
if (name == null || Collections.frequency(this.names.values(), name) > 1) {
return null;
} }
// Only if there is a unique name for this location
return "boot." + name;
} }
public Collection<String> getLocations() {
return this.locations;
}
} }
} }
...@@ -16,30 +16,46 @@ ...@@ -16,30 +16,46 @@
package org.springframework.boot.config; package org.springframework.boot.config;
import java.util.ArrayList; import java.util.Random;
import java.util.List;
import org.springframework.core.env.Environment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.DigestUtils;
/** /**
* Default implementation of {@link PropertySourceLoadersFactory}. Provides a * {@link PropertySource} that returns a random value for any property that starts with
* {@link PropertiesPropertySourceLoader} and when possible a * {@literal "random."}. Return a {@code byte[]} unless the property name ends with
* {@link YamlPropertySourceLoader}. * {@literal ".int} or {@literal ".long"}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class DefaultPropertySourceLoadersFactory implements PropertySourceLoadersFactory { public class RandomValuePropertySource extends PropertySource<Random> {
public RandomValuePropertySource(String name) {
super(name, new Random());
}
@Override @Override
public List<PropertySourceLoader> getLoaders(Environment environment) { public Object getProperty(String name) {
ArrayList<PropertySourceLoader> loaders = new ArrayList<PropertySourceLoader>(); if (!name.startsWith("random.")) {
loaders.add(new PropertiesPropertySourceLoader()); return null;
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { }
loaders.add(YamlPropertySourceLoader.springProfileAwareLoader(environment if (name.endsWith("int")) {
.getActiveProfiles())); return getSource().nextInt();
} }
return loaders; if (name.endsWith("long")) {
return getSource().nextLong();
}
byte[] bytes = new byte[32];
getSource().nextBytes(bytes);
return DigestUtils.md5DigestAsHex(bytes);
}
public static void addToEnvironment(ConfigurableEnvironment environment) {
environment.getPropertySources().addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource("random"));
} }
} }
/*
* Copyright 2012-2013 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.boot.config;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.springframework.boot.yaml.DefaultProfileDocumentMatcher;
import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
import org.springframework.boot.yaml.YamlProcessor.DocumentMatcher;
import org.springframework.boot.yaml.YamlProcessor.MatchStatus;
import org.springframework.boot.yaml.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* Strategy to load '.yml' files into a {@link PropertySource}.
*
* @author Dave Syer
*/
public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader {
private final List<DocumentMatcher> matchers;
/**
* Create a {@link YamlPropertySourceLoader} instance with the specified matchers.
* @param matchers the document matchers
*/
public YamlPropertySourceLoader(DocumentMatcher... matchers) {
this.matchers = Arrays.asList(matchers);
}
@Override
public boolean supports(Resource resource) {
return resource.getFilename().endsWith(".yml");
}
@Override
protected Properties loadProperties(final Resource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
if (this.matchers != null && !this.matchers.isEmpty()) {
factory.setMatchDefault(false);
factory.setDocumentMatchers(this.matchers);
}
factory.setResources(new Resource[] { resource });
return factory.getObject();
}
/**
* A property source loader that loads all properties and matches all documents.
* @return a property source loader
*/
public static YamlPropertySourceLoader matchAllLoader() {
return new YamlPropertySourceLoader();
}
/**
* A property source loader that matches documents that have no explicit profile or
* which have an explicit "spring.profiles.active" value in the current active
* profiles.
* @param activeProfiles the active profiles to match independent of file contents
* @return a property source loader
*/
public static YamlPropertySourceLoader springProfileAwareLoader(
String[] activeProfiles) {
final SpringProfileDocumentMatcher matcher = new SpringProfileDocumentMatcher();
for (String profile : activeProfiles) {
matcher.addActiveProfiles(profile);
}
return new YamlPropertySourceLoader(matcher, new DefaultProfileDocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
MatchStatus result = super.matches(properties);
if (result == MatchStatus.FOUND) {
Set<String> profiles = StringUtils.commaDelimitedListToSet(properties
.getProperty("spring.profiles.active", ""));
for (String profile : profiles) {
// allow document with no profile to set the active one
matcher.addActiveProfiles(profile);
}
}
return result;
}
});
}
}
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.io.IOException;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
...@@ -26,9 +28,7 @@ import org.springframework.beans.factory.ListableBeanFactory; ...@@ -26,9 +28,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.boot.config.PropertiesPropertySourceLoader; import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.boot.config.PropertySourceLoader;
import org.springframework.boot.config.YamlPropertySourceLoader;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -332,26 +332,22 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc ...@@ -332,26 +332,22 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
return this.validator; return this.validator;
} }
private PropertySources loadPropertySources(String[] path) { private PropertySources loadPropertySources(String[] locations) {
MutablePropertySources propertySources = new MutablePropertySources(); try {
PropertySourceLoader[] loaders = { PropertySourcesLoader loader = new PropertySourcesLoader();
new PropertiesPropertySourceLoader(), for (String location : locations) {
YamlPropertySourceLoader.springProfileAwareLoader(this.environment Resource resource = this.resourceLoader.getResource(this.environment
.getActiveProfiles()) }; .resolvePlaceholders(location));
for (String location : path) { for (String profile : this.environment.getActiveProfiles()) {
location = this.environment.resolvePlaceholders(location); loader.load(resource, null, profile);
Resource resource = this.resourceLoader.getResource(location);
if (resource != null && resource.exists()) {
for (PropertySourceLoader loader : loaders) {
if (loader.supports(resource)) {
PropertySource<?> propertySource = loader.load(
resource.getDescription(), resource);
propertySources.addFirst(propertySource);
} }
loader.load(resource, null, null);
} }
return loader.getPropertySources();
} }
catch (IOException ex) {
throw new IllegalStateException(ex);
} }
return propertySources;
} }
private ConversionService getDefaultConversionService() { private ConversionService getDefaultConversionService() {
......
/* /*
* Copyright 2012-2013 the original author or authors. * Copyright 2012-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,13 +14,10 @@ ...@@ -14,13 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.config; package org.springframework.boot.env;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -30,34 +27,22 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; ...@@ -30,34 +27,22 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
* Strategy to load '.properties' files into a {@link PropertySource}. * Strategy to load '.properties' files into a {@link PropertySource}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
public class PropertiesPropertySourceLoader implements PropertySourceLoader { public class PropertiesPropertySourceLoader implements PropertySourceLoader {
private static Log logger = LogFactory.getLog(PropertiesPropertySourceLoader.class);
@Override @Override
public boolean supports(Resource resource) { public String[] getFileExtensions() {
return resource.getFilename().endsWith(".properties"); return new String[] { "properties" };
} }
@Override @Override
public PropertySource<?> load(String name, Resource resource) { public PropertySource<?> load(String name, Resource resource, String profile)
try { throws IOException {
Properties properties = loadProperties(resource); if (profile != null) {
// N.B. this is off by default unless user has supplied logback config in return null;
// standard location
if (logger.isDebugEnabled()) {
logger.debug("Properties loaded from " + resource + ": " + properties);
}
return new PropertiesPropertySource(name, properties);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load properties from " + resource,
ex);
} }
} return new PropertiesPropertySource(name,
PropertiesLoaderUtils.loadProperties(resource));
protected Properties loadProperties(Resource resource) throws IOException {
return PropertiesLoaderUtils.loadProperties(resource);
} }
} }
...@@ -14,29 +14,39 @@ ...@@ -14,29 +14,39 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.config; package org.springframework.boot.env;
import java.io.IOException;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.SpringFactoriesLoader;
/** /**
* Strategy interface used to load a {@link PropertySource}. * Strategy interface located via {@link SpringFactoriesLoader} and used to load a
* {@link PropertySource}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
public interface PropertySourceLoader { public interface PropertySourceLoader {
/** /**
* Returns {@code true} if the {@link Resource} is supported. * Returns the file extensions that the loader supports (excluding the '.').
* @return if the resource is supported
*/ */
boolean supports(Resource resource); String[] getFileExtensions();
/** /**
* Load the resource into a property source. * Load the resource into a property source.
* @param name the name of the property source * @param name the name of the property source
* @return a property source * @param resource the resource to load
* @param profile the name of the profile to load or {@code null}. The profile can be
* used to load multi-document files (such as YAML). Simple property formats should
* {@code null} when asked to load a profile.
* @return a property source or {@code null}
* @throws IOException
*/ */
PropertySource<?> load(String name, Resource resource); PropertySource<?> load(String name, Resource resource, String profile)
throws IOException;
} }
/*
* Copyright 2012-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.boot.env;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
/**
* Utiltiy that can be used to {@link MutablePropertySources} using
* {@link PropertySourceLoader}s.
*
* @author Phillip Webb
*/
public class PropertySourcesLoader {
private final MutablePropertySources propertySources;
private final List<PropertySourceLoader> loaders;
/**
* Create a new {@link PropertySourceLoader} instance backed by a new
* {@link MutablePropertySources}.
*/
public PropertySourcesLoader() {
this(new MutablePropertySources());
}
/**
* Create a new {@link PropertySourceLoader} instance backed by the specified
* {@link MutablePropertySources}.
* @param propertySources the destination property sources
*/
public PropertySourcesLoader(MutablePropertySources propertySources) {
Assert.notNull(propertySources, "PropertySources must not be null");
this.propertySources = propertySources;
this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
null);
}
/**
* Load the specified resource (if possible) and add it as the first source.
* @param resource the source resource (may be {@code null}).
* @param name the root property name (may be {@code null}).
* @param profile a specific profile to load or {@code null} to load the default.
* @return the loaded property source or {@code null}
* @throws IOException
*/
public PropertySource<?> load(Resource resource, String name, String profile)
throws IOException {
if (resource != null && resource.exists()) {
name = generatePropertySourceName(resource, name, profile);
for (PropertySourceLoader loader : this.loaders) {
if (canLoadFileExtension(loader, resource)) {
PropertySource<?> source = loader.load(name, resource, profile);
addPropertySource(source);
return source;
}
}
}
return null;
}
private String generatePropertySourceName(Resource resource, String name,
String profile) {
if (name == null) {
name = resource.getDescription();
}
return (profile == null ? name : name + "#" + profile);
}
private boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) {
String filename = resource.getFilename().toLowerCase();
for (String extension : loader.getFileExtensions()) {
if (filename.endsWith("." + extension.toLowerCase())) {
return true;
}
}
return false;
}
private void addPropertySource(PropertySource<?> propertySource) {
if (propertySource != null) {
this.propertySources.addLast(propertySource);
}
}
/**
* Return the {@link MutablePropertySources} being loaded.
*/
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
/**
* Returns all file extensions that could be loaded.
*/
public Set<String> getAllFileExtensions() {
Set<String> fileExtensions = new HashSet<String>();
for (PropertySourceLoader loader : this.loaders) {
fileExtensions.addAll(Arrays.asList(loader.getFileExtensions()));
}
return fileExtensions;
}
}
...@@ -14,26 +14,51 @@ ...@@ -14,26 +14,51 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.config; package org.springframework.boot.env;
import java.util.List; import java.io.IOException;
import java.util.Properties;
import org.springframework.core.env.Environment; import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
import org.springframework.boot.yaml.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
/** /**
* Factory to return {@link PropertySourceLoader}s. * Strategy to load '.yml' files into a {@link PropertySource}.
* *
* @author Dave Syer * @author Dave Syer
* @see DefaultPropertySourceLoadersFactory * @author Phillip Webb
*/ */
public interface PropertySourceLoadersFactory { public class YamlPropertySourceLoader implements PropertySourceLoader {
/** @Override
* Return a list of {@link PropertySourceLoader}s in the order that they should be public String[] getFileExtensions() {
* tried. return new String[] { "yml" };
* @param environment the source environment }
* @return a list of loaders
*/ @Override
List<PropertySourceLoader> getLoaders(Environment environment); public PropertySource<?> load(String name, Resource resource, String profile)
throws IOException {
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
if (profile == null) {
factory.setMatchDefault(true);
factory.setDocumentMatchers(new SpringProfileDocumentMatcher());
}
else {
factory.setMatchDefault(false);
factory.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));
}
factory.setResources(new Resource[] { resource });
Properties properties = factory.getObject();
if (profile == null || !properties.isEmpty()) {
return new PropertiesPropertySource(name, properties);
}
}
return null;
}
} }
...@@ -37,6 +37,13 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher { ...@@ -37,6 +37,13 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher {
private String[] activeProfiles = new String[0]; private String[] activeProfiles = new String[0];
public SpringProfileDocumentMatcher() {
}
public SpringProfileDocumentMatcher(String... profiles) {
addActiveProfiles(profiles);
}
public void addActiveProfiles(String... profiles) { public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<String>( LinkedHashSet<String> set = new LinkedHashSet<String>(
Arrays.asList(this.activeProfiles)); Arrays.asList(this.activeProfiles));
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
package org.springframework.boot.yaml; package org.springframework.boot.yaml;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
...@@ -37,7 +39,7 @@ import org.yaml.snakeyaml.Yaml; ...@@ -37,7 +39,7 @@ import org.yaml.snakeyaml.Yaml;
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class YamlProcessor { public abstract class YamlProcessor {
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
...@@ -75,14 +77,15 @@ public class YamlProcessor { ...@@ -75,14 +77,15 @@ public class YamlProcessor {
* </pre> * </pre>
* @param matchers a map of keys to value patterns (regular expressions) * @param matchers a map of keys to value patterns (regular expressions)
*/ */
public void setDocumentMatchers(List<? extends DocumentMatcher> matchers) { public void setDocumentMatchers(DocumentMatcher... matchers) {
this.documentMatchers = Collections.unmodifiableList(matchers); this.documentMatchers = Collections
.unmodifiableList(new ArrayList<DocumentMatcher>(Arrays.asList(matchers)));
} }
/** /**
* Flag indicating that a document for which all the * Flag indicating that a document for which all the
* {@link #setDocumentMatchers(List) document matchers} abstain will nevertheless * {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain will
* match. * nevertheless match.
* @param matchDefault the flag to set (default true) * @param matchDefault the flag to set (default true)
*/ */
public void setMatchDefault(boolean matchDefault) { public void setMatchDefault(boolean matchDefault) {
...@@ -111,10 +114,10 @@ public class YamlProcessor { ...@@ -111,10 +114,10 @@ public class YamlProcessor {
/** /**
* Provides an opportunity for subclasses to process the Yaml parsed from the supplied * Provides an opportunity for subclasses to process the Yaml parsed from the supplied
* resources. Each resource is parsed in turn and the documents inside checked against * resources. Each resource is parsed in turn and the documents inside checked against
* the {@link #setDocumentMatchers(List) matchers}. If a document matches it is passed * the {@link #setDocumentMatchers(DocumentMatcher...) matchers}. If a document
* into the callback, along with its representation as Properties. Depending on the * matches it is passed into the callback, along with its representation as
* {@link #setResolutionMethod(ResolutionMethod)} not all of the documents will be * Properties. Depending on the {@link #setResolutionMethod(ResolutionMethod)} not all
* parsed. * of the documents will be parsed.
* @param callback a callback to delegate to once matching documents are found * @param callback a callback to delegate to once matching documents are found
*/ */
protected void process(MatchCallback callback) { protected void process(MatchCallback callback) {
...@@ -172,6 +175,7 @@ public class YamlProcessor { ...@@ -172,6 +175,7 @@ public class YamlProcessor {
result.put("document", object); result.put("document", object);
return result; return result;
} }
Map<Object, Object> map = (Map<Object, Object>) object; Map<Object, Object> map = (Map<Object, Object>) object;
for (Entry<Object, Object> entry : map.entrySet()) { for (Entry<Object, Object> entry : map.entrySet()) {
Object value = entry.getValue(); Object value = entry.getValue();
...@@ -191,16 +195,18 @@ public class YamlProcessor { ...@@ -191,16 +195,18 @@ public class YamlProcessor {
} }
private boolean process(Map<String, Object> map, MatchCallback callback) { private boolean process(Map<String, Object> map, MatchCallback callback) {
Properties properties = new Properties(); Properties properties = new Properties();
assignProperties(properties, map, null); assignProperties(properties, map, null);
if (this.documentMatchers.isEmpty()) { if (this.documentMatchers.isEmpty()) {
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {
this.logger.debug("Merging document (no matchers set)" + map); this.logger.debug("Merging document (no matchers set)" + map);
} }
callback.process(properties, map); callback.process(properties, map);
return true;
} }
else {
boolean valueFound = false;
MatchStatus result = MatchStatus.ABSTAIN; MatchStatus result = MatchStatus.ABSTAIN;
for (DocumentMatcher matcher : this.documentMatchers) { for (DocumentMatcher matcher : this.documentMatchers) {
MatchStatus match = matcher.matches(properties); MatchStatus match = matcher.matches(properties);
...@@ -211,24 +217,21 @@ public class YamlProcessor { ...@@ -211,24 +217,21 @@ public class YamlProcessor {
+ properties); + properties);
} }
callback.process(properties, map); callback.process(properties, map);
valueFound = true; return true;
// No need to check for more matches
break;
} }
} }
if (result == MatchStatus.ABSTAIN && this.matchDefault) { if (result == MatchStatus.ABSTAIN && this.matchDefault) {
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {
this.logger.debug("Matched document with default matcher: " + map); this.logger.debug("Matched document with default matcher: " + map);
} }
callback.process(properties, map); callback.process(properties, map);
return true;
} }
else if (!valueFound) {
this.logger.debug("Unmatched document"); this.logger.debug("Unmatched document");
return false; return false;
} }
}
return true;
}
private void assignProperties(Properties properties, Map<String, Object> input, private void assignProperties(Properties properties, Map<String, Object> input,
String path) { String path) {
...@@ -300,7 +303,21 @@ public class YamlProcessor { ...@@ -300,7 +303,21 @@ public class YamlProcessor {
* Status returned from {@link DocumentMatcher#matches(Properties)} * Status returned from {@link DocumentMatcher#matches(Properties)}
*/ */
public static enum MatchStatus { public static enum MatchStatus {
FOUND, NOT_FOUND, ABSTAIN;
/**
* A match was found.
*/
FOUND,
/**
* No match was found.
*/
NOT_FOUND,
/**
* The matcher should not be considered.
*/
ABSTAIN;
/** /**
* Compare two {@link MatchStatus} items, returning the most specific status. * Compare two {@link MatchStatus} items, returning the most specific status.
......
# ProperySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Participants # Run Participants
org.springframework.boot.SpringApplicationRunParticipant=\ org.springframework.boot.SpringApplicationRunParticipant=\
org.springframework.boot.event.EventPublishingRunParticipant org.springframework.boot.event.EventPublishingRunParticipant
......
...@@ -43,6 +43,36 @@ public class ReproTests { ...@@ -43,6 +43,36 @@ public class ReproTests {
assertThat(context.getEnvironment().acceptsProfiles("a"), equalTo(true)); assertThat(context.getEnvironment().acceptsProfiles("a"), equalTo(true));
} }
@Test
public void activeProfilesWithYaml() throws Exception {
// gh-322
SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false);
String configName = "--spring.config.name=activeprofilerepro";
assertVersionProperty(application.run(configName, "--spring.profiles.active=B"),
"B", "B");
assertVersionProperty(application.run(configName), "B", "B");
assertVersionProperty(application.run(configName, "--spring.profiles.active=A"),
"A", "A");
assertVersionProperty(application.run(configName, "--spring.profiles.active=C"),
"C", "C");
assertVersionProperty(
application.run(configName, "--spring.profiles.active=A,C"), "A", "A",
"C");
assertVersionProperty(
application.run(configName, "--spring.profiles.active=C,A"), "C", "C",
"A");
}
private void assertVersionProperty(ConfigurableApplicationContext context,
String expectedVersion, String... expectedActiveProfiles) {
assertThat(context.getEnvironment().getActiveProfiles(),
equalTo(expectedActiveProfiles));
assertThat("version mismatch", context.getEnvironment().getProperty("version"),
equalTo(expectedVersion));
context.close();
}
@Configuration @Configuration
public static class Config { public static class Config {
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.boot.config; package org.springframework.boot.config;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
...@@ -27,6 +26,7 @@ import org.junit.Rule; ...@@ -27,6 +26,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.config.ConfigFileApplicationListener.ConfigurationPropertySources;
import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -34,10 +34,9 @@ import org.springframework.context.annotation.Configuration; ...@@ -34,10 +34,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
...@@ -72,7 +71,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -72,7 +71,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void loadPropertiesFile() throws Exception { public void loadPropertiesFile() throws Exception {
this.initializer.setNames("testproperties"); this.initializer.setSearchNames("testproperties");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("frompropertiesfile")); assertThat(property, equalTo("frompropertiesfile"));
...@@ -108,7 +107,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -108,7 +107,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void loadTwoPropertiesFiles() throws Exception { public void loadTwoPropertiesFiles() throws Exception {
this.initializer.setNames("moreproperties,testproperties"); this.initializer.setSearchNames("moreproperties,testproperties");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("frommorepropertiesfile")); assertThat(property, equalTo("frommorepropertiesfile"));
...@@ -116,7 +115,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -116,7 +115,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void loadYamlFile() throws Exception { public void loadYamlFile() throws Exception {
this.initializer.setNames("testyaml"); this.initializer.setSearchNames("testyaml");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("fromyamlfile")); assertThat(property, equalTo("fromyamlfile"));
...@@ -128,7 +127,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -128,7 +127,7 @@ public class ConfigFileApplicationListenerTests {
public void commandLineWins() throws Exception { public void commandLineWins() throws Exception {
this.environment.getPropertySources().addFirst( this.environment.getPropertySources().addFirst(
new SimpleCommandLinePropertySource("--my.property=fromcommandline")); new SimpleCommandLinePropertySource("--my.property=fromcommandline"));
this.initializer.setNames("testproperties"); this.initializer.setSearchNames("testproperties");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("fromcommandline")); assertThat(property, equalTo("fromcommandline"));
...@@ -137,7 +136,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -137,7 +136,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void systemPropertyWins() throws Exception { public void systemPropertyWins() throws Exception {
System.setProperty("my.property", "fromsystem"); System.setProperty("my.property", "fromsystem");
this.initializer.setNames("testproperties"); this.initializer.setSearchNames("testproperties");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("fromsystem")); assertThat(property, equalTo("fromsystem"));
...@@ -145,7 +144,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -145,7 +144,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void loadPropertiesThenProfileProperties() throws Exception { public void loadPropertiesThenProfileProperties() throws Exception {
this.initializer.setNames("enableprofile"); this.initializer.setSearchNames("enableprofile");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("fromprofilepropertiesfile")); assertThat(property, equalTo("fromprofilepropertiesfile"));
...@@ -153,7 +152,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -153,7 +152,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void profilePropertiesUsedInPlaceholders() throws Exception { public void profilePropertiesUsedInPlaceholders() throws Exception {
this.initializer.setNames("enableprofile"); this.initializer.setSearchNames("enableprofile");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("one.more"); String property = this.environment.getProperty("one.more");
assertThat(property, equalTo("fromprofilepropertiesfile")); assertThat(property, equalTo("fromprofilepropertiesfile"));
...@@ -161,7 +160,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -161,7 +160,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void yamlProfiles() throws Exception { public void yamlProfiles() throws Exception {
this.initializer.setNames("testprofiles"); this.initializer.setSearchNames("testprofiles");
this.environment.setActiveProfiles("dev"); this.environment.setActiveProfiles("dev");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
...@@ -172,7 +171,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -172,7 +171,7 @@ public class ConfigFileApplicationListenerTests {
@Test @Test
public void yamlSetsProfiles() throws Exception { public void yamlSetsProfiles() throws Exception {
this.initializer.setNames("testsetprofiles"); this.initializer.setSearchNames("testsetprofiles");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(Arrays.asList(this.environment.getActiveProfiles()), contains("dev")); assertThat(Arrays.asList(this.environment.getActiveProfiles()), contains("dev"));
...@@ -183,7 +182,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -183,7 +182,7 @@ public class ConfigFileApplicationListenerTests {
public void yamlProfileCanBeChanged() throws Exception { public void yamlProfileCanBeChanged() throws Exception {
EnvironmentTestUtils.addEnvironment(this.environment, EnvironmentTestUtils.addEnvironment(this.environment,
"spring.profiles.active:prod"); "spring.profiles.active:prod");
this.initializer.setNames("testsetprofiles"); this.initializer.setSearchNames("testsetprofiles");
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
assertThat(this.environment.getActiveProfiles(), equalTo(new String[] { "prod" })); assertThat(this.environment.getActiveProfiles(), equalTo(new String[] { "prod" }));
} }
...@@ -206,10 +205,11 @@ public class ConfigFileApplicationListenerTests { ...@@ -206,10 +205,11 @@ public class ConfigFileApplicationListenerTests {
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(property, equalTo("fromspecificlocation")); assertThat(property, equalTo("fromspecificlocation"));
assertThat(this.environment, containsProperySource(location)); assertThat(this.environment, containsProperySource("applicationConfig: "
+ "class path resource [specificlocation.properties]"));
// The default property source is still there // The default property source is still there
assertThat(this.environment, containsProperySource("classpath:" assertThat(this.environment, containsProperySource("applicationConfig: "
+ "/application.properties")); + "class path resource [application.properties]"));
assertThat(this.environment.getProperty("foo"), equalTo("bucket")); assertThat(this.environment.getProperty("foo"), equalTo("bucket"));
} }
...@@ -219,35 +219,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -219,35 +219,8 @@ public class ConfigFileApplicationListenerTests {
EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:"
+ location); + location);
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
assertThat(this.environment, containsProperySource(location)); assertThat(this.environment, containsProperySource("applicationConfig: "
} + "URL [" + location + "]"));
@Test
public void unsupportedResource() throws Exception {
this.initializer
.setPropertySourceLoadersFactory(new PropertySourceLoadersFactory() {
@Override
public List<PropertySourceLoader> getLoaders(Environment environment) {
return Arrays
.<PropertySourceLoader> asList(new PropertySourceLoader() {
@Override
public boolean supports(Resource resource) {
return false;
}
@Override
public org.springframework.core.env.PropertySource<?> load(
String name, Resource resource) {
return null;
}
});
}
});
this.expected.expect(IllegalStateException.class);
this.expected.expectMessage("No supported loader");
this.initializer.onApplicationEvent(this.event);
} }
@Test @Test
...@@ -256,7 +229,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -256,7 +229,8 @@ public class ConfigFileApplicationListenerTests {
EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:"
+ location); + location);
this.initializer.onApplicationEvent(this.event); this.initializer.onApplicationEvent(this.event);
assertThat(this.environment, containsProperySource("file:" + location)); assertThat(this.environment, containsProperySource("applicationConfig: "
+ "URL [file:" + location + "]"));
} }
@Test @Test
...@@ -266,8 +240,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -266,8 +240,8 @@ public class ConfigFileApplicationListenerTests {
ConfigurableApplicationContext context = application.run(); ConfigurableApplicationContext context = application.run();
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("fromspecificlocation")); assertThat(property, equalTo("fromspecificlocation"));
assertThat(context.getEnvironment(), containsProperySource("classpath:" assertThat(context.getEnvironment(), containsProperySource("class path resource "
+ "/specificlocation.properties")); + "[specificlocation.properties]"));
context.close(); context.close();
} }
...@@ -282,8 +256,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -282,8 +256,8 @@ public class ConfigFileApplicationListenerTests {
ConfigurableApplicationContext context = application.run(); ConfigurableApplicationContext context = application.run();
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("fromspecificlocation")); assertThat(property, equalTo("fromspecificlocation"));
assertThat(context.getEnvironment(), containsProperySource("classpath:" assertThat(context.getEnvironment(), containsProperySource("class path resource "
+ "/specificlocation.properties")); + "[specificlocation.properties]"));
context.close(); context.close();
} }
...@@ -295,10 +269,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -295,10 +269,7 @@ public class ConfigFileApplicationListenerTests {
ConfigurableApplicationContext context = application.run(); ConfigurableApplicationContext context = application.run();
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("fromspecificlocation")); assertThat(property, equalTo("fromspecificlocation"));
// In this case "foo" should be the specificlocation.properties source, but Spring assertThat(context.getEnvironment(), containsProperySource("foo"));
// will have shifted it to the back of the line.
assertThat(context.getEnvironment().getPropertySources().get("boot.foo"),
notNullValue());
context.close(); context.close();
} }
...@@ -311,10 +282,10 @@ public class ConfigFileApplicationListenerTests { ...@@ -311,10 +282,10 @@ public class ConfigFileApplicationListenerTests {
.run("--spring.profiles.active=myprofile"); .run("--spring.profiles.active=myprofile");
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("frompropertiesfile")); assertThat(property, equalTo("frompropertiesfile"));
assertThat(context.getEnvironment(), containsProperySource("classpath:" assertThat(context.getEnvironment(), containsProperySource("class path resource "
+ "/enableprofile.properties")); + "[enableprofile.properties]"));
assertThat(context.getEnvironment(), not(containsProperySource("classpath:" assertThat(context.getEnvironment(), not(containsProperySource("classpath:/"
+ "/enableprofile-myprofile.properties"))); + "enableprofile-myprofile.properties")));
context.close(); context.close();
} }
...@@ -339,8 +310,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -339,8 +310,8 @@ public class ConfigFileApplicationListenerTests {
ConfigurableApplicationContext context = application.run(); ConfigurableApplicationContext context = application.run();
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("frommorepropertiesfile")); assertThat(property, equalTo("frommorepropertiesfile"));
assertThat(context.getEnvironment(), assertThat(context.getEnvironment(), containsProperySource("class path resource "
containsProperySource("classpath:/specificlocation.properties")); + "[specificlocation.properties]"));
context.close(); context.close();
} }
...@@ -352,12 +323,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -352,12 +323,7 @@ public class ConfigFileApplicationListenerTests {
ConfigurableApplicationContext context = application.run(); ConfigurableApplicationContext context = application.run();
String property = context.getEnvironment().getProperty("my.property"); String property = context.getEnvironment().getProperty("my.property");
assertThat(property, equalTo("frommorepropertiesfile")); assertThat(property, equalTo("frommorepropertiesfile"));
// foo is there but it is a dead rubber because the individual sources get higher assertThat(context.getEnvironment(), containsProperySource("foo"));
// priority (and are named after the resource locations)
assertThat(context.getEnvironment().getPropertySources().get("foo"),
notNullValue());
assertThat(context.getEnvironment(),
containsProperySource("classpath:/specificlocation.properties"));
context.close(); context.close();
} }
...@@ -377,7 +343,6 @@ public class ConfigFileApplicationListenerTests { ...@@ -377,7 +343,6 @@ public class ConfigFileApplicationListenerTests {
private static Matcher<? super ConfigurableEnvironment> containsProperySource( private static Matcher<? super ConfigurableEnvironment> containsProperySource(
final String sourceName) { final String sourceName) {
return new TypeSafeDiagnosingMatcher<ConfigurableEnvironment>() { return new TypeSafeDiagnosingMatcher<ConfigurableEnvironment>() {
@Override @Override
public void describeTo(Description description) { public void describeTo(Description description) {
description.appendText("environment containing property source ") description.appendText("environment containing property source ")
...@@ -387,9 +352,12 @@ public class ConfigFileApplicationListenerTests { ...@@ -387,9 +352,12 @@ public class ConfigFileApplicationListenerTests {
@Override @Override
protected boolean matchesSafely(ConfigurableEnvironment item, protected boolean matchesSafely(ConfigurableEnvironment item,
Description mismatchDescription) { Description mismatchDescription) {
mismatchDescription.appendText("Not matched against: ").appendValue( MutablePropertySources sources = new MutablePropertySources(
item.getPropertySources()); item.getPropertySources());
return item.getPropertySources().contains(sourceName); ConfigurationPropertySources.finishAndRelocate(sources);
mismatchDescription.appendText("Not matched against: ").appendValue(
sources);
return sources.contains(sourceName);
} }
}; };
} }
......
...@@ -36,7 +36,8 @@ import static org.junit.Assert.assertEquals; ...@@ -36,7 +36,8 @@ import static org.junit.Assert.assertEquals;
*/ */
public class YamlProcessorTests { public class YamlProcessorTests {
private final YamlProcessor processor = new YamlProcessor(); private final YamlProcessor processor = new YamlProcessor() {
};
@Rule @Rule
public ExpectedException exception = ExpectedException.none(); public ExpectedException exception = ExpectedException.none();
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package org.springframework.boot.yaml; package org.springframework.boot.yaml;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
...@@ -33,7 +32,9 @@ import org.springframework.core.io.Resource; ...@@ -33,7 +32,9 @@ import org.springframework.core.io.Resource;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.scanner.ScannerException; import org.yaml.snakeyaml.scanner.ScannerException;
import static org.junit.Assert.assertEquals; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/** /**
* Tests for {@link YamlPropertiesFactoryBean}. * Tests for {@link YamlPropertiesFactoryBean}.
...@@ -51,8 +52,8 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -51,8 +52,8 @@ public class YamlPropertiesFactoryBeanTests {
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam:\n foo: baz".getBytes()) }); "foo: bar\nspam:\n foo: baz".getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bar"));
assertEquals("baz", properties.get("spam.foo")); assertThat(properties.getProperty("spam.foo"), equalTo("baz"));
} }
@Test @Test
...@@ -72,31 +73,29 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -72,31 +73,29 @@ public class YamlPropertiesFactoryBeanTests {
new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()), new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()),
new ByteArrayResource("foo:\n bar: spam".getBytes()) }); new ByteArrayResource("foo:\n bar: spam".getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bar"));
assertEquals("baz", properties.get("spam.foo")); assertThat(properties.getProperty("spam.foo"), equalTo("baz"));
assertEquals("spam", properties.get("foo.bar")); assertThat(properties.getProperty("foo.bar"), equalTo("spam"));
} }
@Test @Test
@Ignore @Ignore("We can't fail on duplicate keys because the Map is created by the YAML library")
// We can't fail on duplicate keys because the Map is created by the YAML library
public void testLoadResourcesWithInternalOverride() throws Exception { public void testLoadResourcesWithInternalOverride() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes()) }); "foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bar"));
} }
@Test @Test
@Ignore @Ignore("We can't fail on duplicate keys because the Map is created by the YAML library")
// We can't fail on duplicate keys because the Map is created by the YAML library
public void testLoadResourcesWithNestedInternalOverride() throws Exception { public void testLoadResourcesWithNestedInternalOverride() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes()) }); "foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("spam", properties.get("foo.bar")); assertThat(properties.getProperty("foo.bar"), equalTo("spam"));
} }
@Test @Test
...@@ -105,8 +104,8 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -105,8 +104,8 @@ public class YamlPropertiesFactoryBeanTests {
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam: baz\n---\nfoo: bag".getBytes()) }); "foo: bar\nspam: baz\n---\nfoo: bag".getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bag"));
assertEquals("baz", properties.get("spam")); assertThat(properties.getProperty("spam"), equalTo("baz"));
} }
@Test @Test
...@@ -114,17 +113,16 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -114,17 +113,16 @@ public class YamlPropertiesFactoryBeanTests {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); "foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) });
factory.setDocumentMatchers(Arrays factory.setDocumentMatchers(new DocumentMatcher() {
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override @Override
public MatchStatus matches(Properties properties) { public MatchStatus matches(Properties properties) {
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND; : MatchStatus.NOT_FOUND;
} }
})); });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bag"));
assertEquals("bad", properties.get("spam")); assertThat(properties.getProperty("spam"), equalTo("bad"));
} }
@Test @Test
...@@ -133,8 +131,7 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -133,8 +131,7 @@ public class YamlPropertiesFactoryBeanTests {
factory.setMatchDefault(true); factory.setMatchDefault(true);
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); "one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) });
factory.setDocumentMatchers(Arrays factory.setDocumentMatchers(new DocumentMatcher() {
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override @Override
public MatchStatus matches(Properties properties) { public MatchStatus matches(Properties properties) {
if (!properties.containsKey("foo")) { if (!properties.containsKey("foo")) {
...@@ -143,11 +140,33 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -143,11 +140,33 @@ public class YamlPropertiesFactoryBeanTests {
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND; : MatchStatus.NOT_FOUND;
} }
})); });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bag"));
assertEquals("bad", properties.get("spam")); assertThat(properties.getProperty("spam"), equalTo("bad"));
assertEquals("two", properties.get("one")); assertThat(properties.getProperty("one"), equalTo("two"));
}
@Test
public void testLoadResourceWithoutDefaultMatch() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setMatchDefault(false);
factory.setResources(new Resource[] { new ByteArrayResource(
"one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) });
factory.setDocumentMatchers(new DocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
if (!properties.containsKey("foo")) {
return MatchStatus.ABSTAIN;
}
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND;
}
});
Properties properties = factory.getObject();
assertThat(properties.getProperty("foo"), equalTo("bag"));
assertThat(properties.getProperty("spam"), equalTo("bad"));
assertThat(properties.getProperty("one"), nullValue());
} }
@Test @Test
...@@ -156,8 +175,7 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -156,8 +175,7 @@ public class YamlPropertiesFactoryBeanTests {
factory.setMatchDefault(true); factory.setMatchDefault(true);
factory.setResources(new Resource[] { new ByteArrayResource( factory.setResources(new Resource[] { new ByteArrayResource(
"one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes()) }); "one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes()) });
factory.setDocumentMatchers(Arrays factory.setDocumentMatchers(new DocumentMatcher() {
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override @Override
public MatchStatus matches(Properties properties) { public MatchStatus matches(Properties properties) {
if (!properties.containsKey("foo")) { if (!properties.containsKey("foo")) {
...@@ -166,11 +184,11 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -166,11 +184,11 @@ public class YamlPropertiesFactoryBeanTests {
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND; : MatchStatus.NOT_FOUND;
} }
})); });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bag"));
assertEquals("bad", properties.get("spam")); assertThat(properties.getProperty("spam"), equalTo("bad"));
assertEquals("two", properties.get("one")); assertThat(properties.getProperty("one"), equalTo("two"));
} }
@Test @Test
...@@ -179,7 +197,7 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -179,7 +197,7 @@ public class YamlPropertiesFactoryBeanTests {
factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE); factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE);
factory.setResources(new Resource[] { new ClassPathResource("no-such-file.yml") }); factory.setResources(new Resource[] { new ClassPathResource("no-such-file.yml") });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals(0, properties.size()); assertThat(properties.size(), equalTo(0));
} }
@Test @Test
...@@ -188,8 +206,8 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -188,8 +206,8 @@ public class YamlPropertiesFactoryBeanTests {
factory.setResources(new Resource[] { new ByteArrayResource("foo: bar\nspam:" factory.setResources(new Resource[] { new ByteArrayResource("foo: bar\nspam:"
.getBytes()) }); .getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo")); assertThat(properties.getProperty("foo"), equalTo("bar"));
assertEquals("", properties.get("spam")); assertThat(properties.getProperty("spam"), equalTo(""));
} }
@Test @Test
...@@ -198,8 +216,8 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -198,8 +216,8 @@ public class YamlPropertiesFactoryBeanTests {
factory.setResources(new Resource[] { new ByteArrayResource("foo:\n- bar\n- baz" factory.setResources(new Resource[] { new ByteArrayResource("foo:\n- bar\n- baz"
.getBytes()) }); .getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo[0]")); assertThat(properties.getProperty("foo[0]"), equalTo("bar"));
assertEquals("baz", properties.get("foo[1]")); assertThat(properties.getProperty("foo[1]"), equalTo("baz"));
} }
@Test @Test
...@@ -209,10 +227,10 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -209,10 +227,10 @@ public class YamlPropertiesFactoryBeanTests {
"foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four" "foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four"
.getBytes()) }); .getBytes()) });
Properties properties = factory.getObject(); Properties properties = factory.getObject();
assertEquals("crap", properties.get("foo[0].bar.spam")); assertThat(properties.getProperty("foo[0].bar.spam"), equalTo("crap"));
assertEquals("baz", properties.get("foo[1]")); assertThat(properties.getProperty("foo[1]"), equalTo("baz"));
assertEquals("two", properties.get("foo[2].one")); assertThat(properties.getProperty("foo[2].one"), equalTo("two"));
assertEquals("four", properties.get("foo[2].three")); assertThat(properties.getProperty("foo[2].three"), equalTo("four"));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -220,8 +238,9 @@ public class YamlPropertiesFactoryBeanTests { ...@@ -220,8 +238,9 @@ public class YamlPropertiesFactoryBeanTests {
public void testYaml() { public void testYaml() {
Yaml yaml = new Yaml(); Yaml yaml = new Yaml();
Map<String, ?> map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class); Map<String, ?> map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class);
assertEquals("bar", map.get("foo")); assertThat(map.get("foo"), equalTo((Object) "bar"));
assertEquals("baz", ((Map<String, Object>) map.get("spam")).get("foo")); assertThat(((Map<String, Object>) map.get("spam")).get("foo"),
equalTo((Object) "baz"));
} }
} }
spring.profiles.active: B
---
spring.profiles: A
version: A
---
spring.profiles: B
version: B
---
spring.profiles: C
version: C
---
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment