diff --git a/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java index e586963dc7f..cf35a079e7c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/cloudfoundry/VcapApplicationListener.java @@ -98,7 +98,7 @@ public class VcapApplicationListener implements private static final String VCAP_SERVICES = "VCAP_SERVICES"; // 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(); diff --git a/spring-boot/src/main/java/org/springframework/boot/config/ConfigFileApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/config/ConfigFileApplicationListener.java index 2a25d868469..68a26626252 100644 --- a/spring-boot/src/main/java/org/springframework/boot/config/ConfigFileApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/config/ConfigFileApplicationListener.java @@ -16,40 +16,41 @@ package org.springframework.boot.config; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.Queue; import java.util.Set; -import org.springframework.beans.PropertyValues; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; +import org.springframework.boot.env.PropertySourcesLoader; 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.annotation.AnnotatedBeanDefinitionReader; -import org.springframework.context.annotation.PropertySources; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; -import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.util.DigestUtils; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -63,254 +64,99 @@ import org.springframework.util.StringUtils; *
  • file:./config/:
  • * *

    - * Alternative locations and names can be specified using - * {@link #setSearchLocations(String[])} and {@link #setNames(String)}. + * Alternative search locations and names can be specified using + * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}. *

    * 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 * considered. *

    - * The 'spring.config.name' property can be used to specify an alternative name to load or - * alternatively the 'spring.config.location' property can be used to specify an exact - * resource location. + * The 'spring.config.name' property can be used to specify an alternative name to load + * and the 'spring.config.location' property can be used to specify alternative search + * locations or specific files. + *

    + * 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 Phillip Webb */ public class ConfigFileApplicationListener implements - ApplicationListener, Ordered { + ApplicationListener, Ordered { + + private static final String DEFAULT_PROPERTIES = "defaultProperties"; 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:./", - "classpath:/config/", "file:./config/" }; + private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,file:./," + + "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> cache = new HashMap>(); + 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 - public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - Environment environment = event.getEnvironment(); - if (environment instanceof ConfigurableEnvironment) { - configure((ConfigurableEnvironment) environment, event.getSpringApplication()); + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ApplicationEnvironmentPreparedEvent) { + onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } - } - - private void configure(ConfigurableEnvironment environment, - SpringApplication springApplication) { - for (Object source : springApplication.getSources()) { - this.annotations.addFromSource(source); - } - 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); + if (event instanceof ApplicationPreparedEvent) { + onApplicationPreparedEvent((ApplicationPreparedEvent) event); } - } - - 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 - activeProfileAdditionsFromAnySource(environment); - - // Repeatedly load property sources in case additional profiles are activated - int numberOfPropertySources; - do { - numberOfPropertySources = environment.getPropertySources().size(); - activeProfileAdditionsFromAnySource(environment); - loadAgain(environment, resourceLoader, candidates, firstPropertySourceName); - } - while (environment.getPropertySources().size() > numberOfPropertySources); - - if (defaultProperties != null) { - environment.getPropertySources().addLast(defaultProperties); - } - } - - private void activeProfileAdditionsFromAnySource(ConfigurableEnvironment environment) { - for (PropertySource propertySource : environment.getPropertySources()) { - if (propertySource.containsProperty(ACTIVE_PROFILES_PROPERTY)) { - activeProfilesFromProperty(environment, - propertySource.getProperty(ACTIVE_PROFILES_PROPERTY), false); - } - } - } - - private void activeProfilesFromProperty(ConfigurableEnvironment environment, - Object property, boolean addAll) { - for (String profile : StringUtils.commaDelimitedListToSet(property.toString())) { - boolean addition = profile.startsWith("+"); - profile = (addition ? profile.substring(1) : profile); - if (addAll || addition) { - environment.addActiveProfile(profile); - } - } - } + }; - private String loadInitial(ConfigurableEnvironment environment, - ResourceLoader resourceLoader, LoadCandidates candidates) { - String firstSourceName = null; - // Initial load allows profiles to be activated - for (String candidate : candidates) { - for (String path : StringUtils.commaDelimitedListToStringArray(environment - .resolvePlaceholders(candidate))) { - - if (LOCATION_VARIABLE.equals(candidate) && !path.contains("$")) { - if (!path.contains(":")) { - path = "file:" + path; - } - path = StringUtils.cleanPath(path); - } - - PropertySource source = loadPropertySource(environment, - resourceLoader, path, null); - if (source != null) { - if (firstSourceName == null) { - firstSourceName = source.getName(); - } - environment.getPropertySources().addLast(source); - } - } - } - return firstSourceName; - } - - private void loadAgain(ConfigurableEnvironment environment, - ResourceLoader resourceLoader, LoadCandidates candidates, - String firstPropertySourceName) { - for (String profile : environment.getActiveProfiles()) { - for (String candidate : candidates) { - PropertySource source = loadPropertySource(environment, - resourceLoader, candidate, profile); - addBeforeOrLast(environment, firstPropertySourceName, source); - } + private void onApplicationEnvironmentPreparedEvent( + ApplicationEnvironmentPreparedEvent event) { + Environment environment = event.getEnvironment(); + if (environment instanceof ConfigurableEnvironment) { + onApplicationEnvironmentPreparedEvent((ConfigurableEnvironment) environment, + event.getSpringApplication()); } } - private void addBeforeOrLast(ConfigurableEnvironment environment, - String relativePropertySourceName, PropertySource source) { - if (source != null) { - MutablePropertySources propertySources = environment.getPropertySources(); - // Originals go at the end so they don't override the specific profiles - if (relativePropertySourceName != null) { - propertySources.addBefore(relativePropertySourceName, source); + private void onApplicationEnvironmentPreparedEvent( + ConfigurableEnvironment environment, SpringApplication application) { + RandomValuePropertySource.addToEnvironment(environment); + try { + PropertySource defaultProperties = environment.getPropertySources() + .remove(DEFAULT_PROPERTIES); + new Loader(environment).load(); + if (defaultProperties != null) { + environment.getPropertySources().addLast(defaultProperties); } - else { - propertySources.addLast(source); - } - } - } - - private PropertySource loadPropertySource(ConfigurableEnvironment environment, - ResourceLoader resourceLoader, String location, String profile) { - - Class type = this.annotations.configuration(location); - - String suffix = "." + StringUtils.getFilenameExtension(location); - if (StringUtils.hasLength(profile)) { - location = location.replace(suffix, "-" + profile + suffix); } - - if (isPropertySourceAnnotationOnExcludedType(environment, profile, type, location)) { - return null; + catch (IOException ex) { + throw new IllegalStateException("Unable to load configuration files", ex); } - - Resource resource = resourceLoader.getResource(location); - String name = this.annotations.name(location); - name = (name != null ? name : location); - return getPropertySource(environment, name, resource, profile); + bindToSpringApplication(application, environment); } - private boolean isPropertySourceAnnotationOnExcludedType(Environment environment, - String profile, Class type, String location) { - - if (type == null) { - // No configuration class to worry about, just a vanilla properties location - return false; - } - - if (StringUtils.hasText(profile) - && !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( - new DefaultListableBeanFactory(), environment); - int before = reader.getRegistry().getBeanDefinitionCount(); - reader.register(type); - int after = reader.getRegistry().getBeanDefinitionCount(); - - // Return if the configuration class was @Conditional and excluded - return (after == before); + private void bindToSpringApplication(SpringApplication application, + ConfigurableEnvironment environment) { + RelaxedDataBinder binder = new RelaxedDataBinder(application, "spring.main"); + binder.setConversionService(this.conversionService); + binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources())); } - private PropertySource getPropertySource(Environment environment, String name, - Resource resource, String profile) { - if (resource == null || !resource.exists()) { - return null; - } - String key = resource.getDescription() + (profile == null ? "" : "#" + profile); - if (this.cache.containsKey(key)) { - 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); + private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { + ConfigurableApplicationContext context = event.getApplicationContext(); + context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor( + context)); } public void setOrder(int order) { @@ -323,181 +169,233 @@ public class ConfigFileApplicationListener implements } /** - * Sets the names of the files that should be loaded (excluding file extension) as a - * comma separated list. Defaults to "application". + * Set the search locations that will be considered as a comma-separated list. */ - public void setNames(String names) { - this.names = names; + public void setSearchLocations(String locations) { + Assert.hasLength(locations, "Locations must not be empty"); + this.searchLocations = locations; } /** - * Set the search locations that will be considered. + * Sets the names of the files that should be loaded (excluding file extension) as a + * comma-separated list. */ - public void setSearchLocations(String[] searchLocations) { - this.searchLocations = (searchLocations == null ? null : searchLocations.clone()); + public void setSearchNames(String names) { + Assert.hasLength(names, "Names must not be empty"); + this.names = names; } /** - * Set the {@link PropertySourceLoadersFactory} that will be used to create - * {@link PropertySourceLoader}s. + * {@link BeanFactoryPostProcessor} to re-order our property sources below any + * {@code @ProperySource} items added by the {@link ConfigurationClassPostProcessor}. */ - public void setPropertySourceLoadersFactory( - PropertySourceLoadersFactory propertySourceLoaderFactory) { - this.propertySourceLoadersFactory = propertySourceLoaderFactory; - } + private class PropertySourceOrderingPostProcessor implements + BeanFactoryPostProcessor, Ordered { - /** - * Provides {@link Iterable} access to candidate property sources. - */ - private class LoadCandidates implements Iterable { - - private final List candidates; - - public LoadCandidates(ConfigurableEnvironment environment, - ResourceLoader resourceLoader) { - Set candidates = new LinkedHashSet(); - 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(candidates); - Collections.reverse(this.candidates); - } + private ConfigurableApplicationContext context; - private void addLoadCandidatesFromSearchLocations( - ConfigurableEnvironment environment, Set 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 PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) { + this.context = context; } - private void addLoadCandidatesFromAnnotations( - ConfigurableEnvironment environment, ResourceLoader resourceLoader, - Set 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); - } + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; } @Override - public Iterator iterator() { - return this.candidates.iterator(); + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + reorderSources(this.context.getEnvironment()); + } + + private void reorderSources(ConfigurableEnvironment environment) { + ConfigurationPropertySources.finishAndRelocate(environment + .getPropertySources()); + PropertySource defaultProperties = environment.getPropertySources() + .remove(DEFAULT_PROPERTIES); + if (defaultProperties != null) { + environment.getPropertySources().addLast(defaultProperties); + } } } /** - * {@link PropertySource} that returns a random value for any property that starts - * with {@literal "random."}. Return a {@code byte[]} unless the property name ends - * with {@literal ".int} or {@literal ".long"}. + * Loads candidate property sources and configures the active profiles. */ - private static class RandomValuePropertySource extends PropertySource { + private class Loader { - public RandomValuePropertySource(String name) { - super(name, new Random()); - } + private final ConfigurableEnvironment environment; - @Override - public Object getProperty(String name) { - if (!name.startsWith("random.")) { - return null; - } - if (name.endsWith("int")) { - return getSource().nextInt(); - } - if (name.endsWith("long")) { - return getSource().nextLong(); - } - byte[] bytes = new byte[32]; - getSource().nextBytes(bytes); - return DigestUtils.md5DigestAsHex(bytes); - } + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); - } + private PropertySourcesLoader propertiesLoader; - /** - * Holds details collected from - * {@link org.springframework.context.annotation.PropertySource} annotations. - */ - private static class PropertySourceAnnotations { + private Queue profiles; - private final Collection locations = new LinkedHashSet(); + private boolean activatedProfiles; - private final Map names = new HashMap(); + public Loader(ConfigurableEnvironment environment) { + this.environment = environment; + } - private final Map> configs = new HashMap>(); + public void load() throws IOException { + this.propertiesLoader = new PropertySourcesLoader(); + this.profiles = new LinkedList(); + this.profiles.add(null); + this.profiles.addAll(Arrays.asList(this.environment.getActiveProfiles())); + this.activatedProfiles = false; + addActiveProfiles(this.environment.getProperty(ACTIVE_PROFILES_PROPERTY)); + + while (!this.profiles.isEmpty()) { + String profile = this.profiles.poll(); + for (String location : getSearchLocations()) { + for (String name : getSearchNames()) { + load(location, name, profile); + } + } + } - private final Map ignores = new HashMap(); + addConfigurationProperties(this.propertiesLoader.getPropertySources()); + } + + private void load(String location, String name, String profile) + throws IOException { - public void addFromSource(Object source) { - if (source instanceof Class) { - addFromSource((Class) source); + // Try to load directly from the location + PropertySource locationPropertySource = load(location, profile); + + // If that fails, try a search + if (locationPropertySource == null) { + for (String ext : this.propertiesLoader.getAllFileExtensions()) { + if (profile != null) { + // Try the profile specific file (with a null profile section) + load(location + name + "-" + profile + "." + ext, null); + } + // Try the profile (if any) specific section of the normal file + load(location + name + "." + ext, profile); + } } } - private void addFromSource(Class source) { - for (org.springframework.context.annotation.PropertySource propertySource : AnnotationUtils - .getRepeatableAnnotation(source, PropertySources.class, - org.springframework.context.annotation.PropertySource.class)) { - add(source, propertySource); + private PropertySource load(String resourceLocation, String profile) + throws IOException { + Resource resource = this.resourceLoader.getResource(resourceLocation); + if (resource != null && resource.exists()) { + String name = "applicationConfig: " + resource.getDescription(); + if (StringUtils.hasLength(profile)) { + name += " " + profile; + } + PropertySource propertySource = this.propertiesLoader.load(resource, + name, profile); + if (propertySource != null) { + addActiveProfiles(propertySource + .getProperty(ACTIVE_PROFILES_PROPERTY)); + } + return propertySource; } + return null; } - 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()); + private void addActiveProfiles(Object property) { + String profiles = (property == null ? null : property.toString()); + boolean profilesNotActivatedWhenCalled = !this.activatedProfiles; + for (String profile : asResolvedSet(profiles, null)) { + boolean addition = profile.startsWith("+"); + profile = (addition ? profile.substring(1) : profile); + if (profilesNotActivatedWhenCalled || addition) { + this.profiles.add(profile); + this.environment.addActiveProfile(profile); + this.activatedProfiles = true; } } - 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); + } + + public Set getSearchLocations() { + Set locations = new LinkedHashSet(); + 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); } - this.ignores.put(location, reallyIgnore); - this.configs.put(location, source); } + return locations; + } + + public Set getSearchNames() { + if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { + return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), + null); + } + return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); + } + + private Set asResolvedSet(String value, String fallback) { + List list = Arrays.asList(StringUtils + .commaDelimitedListToStringArray(value != null ? this.environment + .resolvePlaceholders(value) : fallback)); + Collections.reverse(list); + return new LinkedHashSet(list); } - public Class configuration(String location) { - return this.configs.get(location); + private void addConfigurationProperties(MutablePropertySources sources) { + List> reorderedSources = new ArrayList>(); + for (PropertySource item : sources) { + reorderedSources.add(item); + } + Collections.reverse(reorderedSources); + this.environment.getPropertySources().addLast( + new ConfigurationPropertySources(reorderedSources)); } - public boolean ignoreResourceNotFound(String location) { - return Boolean.TRUE.equals(this.ignores.get(location)); + } + + /** + * Holds the configuration {@link PropertySource}s as they are loaded can relocate + * them once configuration classes have been processed. + */ + static class ConfigurationPropertySources extends PropertySource { + + private static final String NAME = "applicationConfigurationProperties"; + + private final Collection> sources; + + public ConfigurationPropertySources(Collection> sources) { + super(NAME); + this.sources = sources; } - public String name(String location) { - String name = this.names.get(location); - if (name == null || Collections.frequency(this.names.values(), name) > 1) { - return null; + @Override + public Object getProperty(String name) { + for (PropertySource propertySource : this.sources) { + Object value = propertySource.getProperty(name); + if (value != null) { + return value; + } } - // Only if there is a unique name for this location - return "boot." + name; + return null; } - public Collection getLocations() { - return this.locations; + public static void finishAndRelocate(MutablePropertySources propertySources) { + ConfigurationPropertySources removed = (ConfigurationPropertySources) propertySources + .remove(ConfigurationPropertySources.NAME); + if (removed != null) { + for (PropertySource propertySource : removed.sources) { + propertySources.addLast(propertySource); + } + } } + } } diff --git a/spring-boot/src/main/java/org/springframework/boot/config/DefaultPropertySourceLoadersFactory.java b/spring-boot/src/main/java/org/springframework/boot/config/DefaultPropertySourceLoadersFactory.java deleted file mode 100644 index 4dc7b97e55e..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/config/DefaultPropertySourceLoadersFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.config; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.core.env.Environment; -import org.springframework.util.ClassUtils; - -/** - * Default implementation of {@link PropertySourceLoadersFactory}. Provides a - * {@link PropertiesPropertySourceLoader} and when possible a - * {@link YamlPropertySourceLoader}. - * - * @author Dave Syer - */ -public class DefaultPropertySourceLoadersFactory implements PropertySourceLoadersFactory { - - @Override - public List getLoaders(Environment environment) { - ArrayList loaders = new ArrayList(); - loaders.add(new PropertiesPropertySourceLoader()); - if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { - loaders.add(YamlPropertySourceLoader.springProfileAwareLoader(environment - .getActiveProfiles())); - } - return loaders; - } - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/config/PropertiesPropertySourceLoader.java b/spring-boot/src/main/java/org/springframework/boot/config/PropertiesPropertySourceLoader.java deleted file mode 100644 index ef56f0f2772..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/config/PropertiesPropertySourceLoader.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.Properties; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PropertiesLoaderUtils; - -/** - * Strategy to load '.properties' files into a {@link PropertySource}. - * - * @author Dave Syer - */ -public class PropertiesPropertySourceLoader implements PropertySourceLoader { - - private static Log logger = LogFactory.getLog(PropertiesPropertySourceLoader.class); - - @Override - public boolean supports(Resource resource) { - return resource.getFilename().endsWith(".properties"); - } - - @Override - public PropertySource load(String name, Resource resource) { - try { - Properties properties = loadProperties(resource); - // N.B. this is off by default unless user has supplied logback config in - // 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); - } - } - - protected Properties loadProperties(Resource resource) throws IOException { - return PropertiesLoaderUtils.loadProperties(resource); - } -} \ No newline at end of file diff --git a/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoadersFactory.java b/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoadersFactory.java deleted file mode 100644 index 456d8f97f1a..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoadersFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.config; - -import java.util.List; - -import org.springframework.core.env.Environment; - -/** - * Factory to return {@link PropertySourceLoader}s. - * - * @author Dave Syer - * @see DefaultPropertySourceLoadersFactory - */ -public interface PropertySourceLoadersFactory { - - /** - * Return a list of {@link PropertySourceLoader}s in the order that they should be - * tried. - * @param environment the source environment - * @return a list of loaders - */ - List getLoaders(Environment environment); - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/config/RandomValuePropertySource.java b/spring-boot/src/main/java/org/springframework/boot/config/RandomValuePropertySource.java new file mode 100644 index 00000000000..7a33b023c4b --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/config/RandomValuePropertySource.java @@ -0,0 +1,61 @@ +/* + * 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.config; + +import java.util.Random; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.util.DigestUtils; + +/** + * {@link PropertySource} that returns a random value for any property that starts with + * {@literal "random."}. Return a {@code byte[]} unless the property name ends with + * {@literal ".int} or {@literal ".long"}. + * + * @author Dave Syer + */ +public class RandomValuePropertySource extends PropertySource { + + public RandomValuePropertySource(String name) { + super(name, new Random()); + } + + @Override + public Object getProperty(String name) { + if (!name.startsWith("random.")) { + return null; + } + if (name.endsWith("int")) { + return getSource().nextInt(); + } + 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")); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/config/YamlPropertySourceLoader.java b/spring-boot/src/main/java/org/springframework/boot/config/YamlPropertySourceLoader.java deleted file mode 100644 index 7158497d390..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/config/YamlPropertySourceLoader.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 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 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; - } - }); - } - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 059aa83bdc5..a7d9f2ea35c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties; +import java.io.IOException; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; @@ -26,9 +28,7 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.bind.PropertiesConfigurationFactory; -import org.springframework.boot.config.PropertiesPropertySourceLoader; -import org.springframework.boot.config.PropertySourceLoader; -import org.springframework.boot.config.YamlPropertySourceLoader; +import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; @@ -332,26 +332,22 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc return this.validator; } - private PropertySources loadPropertySources(String[] path) { - MutablePropertySources propertySources = new MutablePropertySources(); - PropertySourceLoader[] loaders = { - new PropertiesPropertySourceLoader(), - YamlPropertySourceLoader.springProfileAwareLoader(this.environment - .getActiveProfiles()) }; - for (String location : path) { - location = this.environment.resolvePlaceholders(location); - 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); - } + private PropertySources loadPropertySources(String[] locations) { + try { + PropertySourcesLoader loader = new PropertySourcesLoader(); + for (String location : locations) { + Resource resource = this.resourceLoader.getResource(this.environment + .resolvePlaceholders(location)); + for (String profile : this.environment.getActiveProfiles()) { + loader.load(resource, null, profile); } + loader.load(resource, null, null); } + return loader.getPropertySources(); + } + catch (IOException ex) { + throw new IllegalStateException(ex); } - return propertySources; } private ConversionService getDefaultConversionService() { diff --git a/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java b/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java new file mode 100644 index 00000000000..dbebdab0859 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java @@ -0,0 +1,48 @@ +/* + * 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 org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PropertiesLoaderUtils; + +/** + * Strategy to load '.properties' files into a {@link PropertySource}. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class PropertiesPropertySourceLoader implements PropertySourceLoader { + + @Override + public String[] getFileExtensions() { + return new String[] { "properties" }; + } + + @Override + public PropertySource load(String name, Resource resource, String profile) + throws IOException { + if (profile != null) { + return null; + } + return new PropertiesPropertySource(name, + PropertiesLoaderUtils.loadProperties(resource)); + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoader.java b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java similarity index 53% rename from spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoader.java rename to spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java index 39f0be1becd..a967fcd4a71 100644 --- a/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoader.java +++ b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java @@ -14,29 +14,39 @@ * 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.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 Phillip Webb */ public interface PropertySourceLoader { /** - * Returns {@code true} if the {@link Resource} is supported. - * @return if the resource is supported + * Returns the file extensions that the loader supports (excluding the '.'). */ - boolean supports(Resource resource); + String[] getFileExtensions(); /** * Load the resource into a 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; } diff --git a/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java new file mode 100644 index 00000000000..742f8245c5f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java @@ -0,0 +1,128 @@ +/* + * 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 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 getAllFileExtensions() { + Set fileExtensions = new HashSet(); + for (PropertySourceLoader loader : this.loaders) { + fileExtensions.addAll(Arrays.asList(loader.getFileExtensions())); + } + return fileExtensions; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java b/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java new file mode 100644 index 00000000000..2fa2a1a039e --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java @@ -0,0 +1,64 @@ +/* + * 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.Properties; + +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; + +/** + * Strategy to load '.yml' files into a {@link PropertySource}. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class YamlPropertySourceLoader implements PropertySourceLoader { + + @Override + public String[] getFileExtensions() { + return new String[] { "yml" }; + } + + @Override + 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; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/yaml/SpringProfileDocumentMatcher.java b/spring-boot/src/main/java/org/springframework/boot/yaml/SpringProfileDocumentMatcher.java index 15724a9ab99..7568493274a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/yaml/SpringProfileDocumentMatcher.java +++ b/spring-boot/src/main/java/org/springframework/boot/yaml/SpringProfileDocumentMatcher.java @@ -37,6 +37,13 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher { private String[] activeProfiles = new String[0]; + public SpringProfileDocumentMatcher() { + } + + public SpringProfileDocumentMatcher(String... profiles) { + addActiveProfiles(profiles); + } + public void addActiveProfiles(String... profiles) { LinkedHashSet set = new LinkedHashSet( Arrays.asList(this.activeProfiles)); @@ -55,4 +62,4 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher { return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties); } -} \ No newline at end of file +} diff --git a/spring-boot/src/main/java/org/springframework/boot/yaml/YamlProcessor.java b/spring-boot/src/main/java/org/springframework/boot/yaml/YamlProcessor.java index 77db233c5e2..73b91408bfe 100644 --- a/spring-boot/src/main/java/org/springframework/boot/yaml/YamlProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/yaml/YamlProcessor.java @@ -17,6 +17,8 @@ package org.springframework.boot.yaml; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -37,7 +39,7 @@ import org.yaml.snakeyaml.Yaml; * * @author Dave Syer */ -public class YamlProcessor { +public abstract class YamlProcessor { private final Log logger = LogFactory.getLog(getClass()); @@ -75,14 +77,15 @@ public class YamlProcessor { * * @param matchers a map of keys to value patterns (regular expressions) */ - public void setDocumentMatchers(List matchers) { - this.documentMatchers = Collections.unmodifiableList(matchers); + public void setDocumentMatchers(DocumentMatcher... matchers) { + this.documentMatchers = Collections + .unmodifiableList(new ArrayList(Arrays.asList(matchers))); } /** * Flag indicating that a document for which all the - * {@link #setDocumentMatchers(List) document matchers} abstain will nevertheless - * match. + * {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain will + * nevertheless match. * @param matchDefault the flag to set (default true) */ public void setMatchDefault(boolean matchDefault) { @@ -111,10 +114,10 @@ public class YamlProcessor { /** * 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 - * the {@link #setDocumentMatchers(List) matchers}. If a document matches it is passed - * into the callback, along with its representation as Properties. Depending on the - * {@link #setResolutionMethod(ResolutionMethod)} not all of the documents will be - * parsed. + * the {@link #setDocumentMatchers(DocumentMatcher...) matchers}. If a document + * matches it is passed into the callback, along with its representation as + * Properties. Depending on the {@link #setResolutionMethod(ResolutionMethod)} not all + * of the documents will be parsed. * @param callback a callback to delegate to once matching documents are found */ protected void process(MatchCallback callback) { @@ -172,6 +175,7 @@ public class YamlProcessor { result.put("document", object); return result; } + Map map = (Map) object; for (Entry entry : map.entrySet()) { Object value = entry.getValue(); @@ -191,43 +195,42 @@ public class YamlProcessor { } private boolean process(Map map, MatchCallback callback) { + Properties properties = new Properties(); assignProperties(properties, map, null); + if (this.documentMatchers.isEmpty()) { if (this.logger.isDebugEnabled()) { this.logger.debug("Merging document (no matchers set)" + map); } callback.process(properties, map); + return true; } - else { - boolean valueFound = false; - MatchStatus result = MatchStatus.ABSTAIN; - for (DocumentMatcher matcher : this.documentMatchers) { - MatchStatus match = matcher.matches(properties); - result = MatchStatus.getMostSpecific(match, result); - if (match == MatchStatus.FOUND) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Matched document with document matcher: " - + properties); - } - callback.process(properties, map); - valueFound = true; - // No need to check for more matches - break; - } - } - if (result == MatchStatus.ABSTAIN && this.matchDefault) { + + MatchStatus result = MatchStatus.ABSTAIN; + for (DocumentMatcher matcher : this.documentMatchers) { + MatchStatus match = matcher.matches(properties); + result = MatchStatus.getMostSpecific(match, result); + if (match == MatchStatus.FOUND) { if (this.logger.isDebugEnabled()) { - this.logger.debug("Matched document with default matcher: " + map); + this.logger.debug("Matched document with document matcher: " + + properties); } callback.process(properties, map); + return true; } - else if (!valueFound) { - this.logger.debug("Unmatched document"); - return false; + } + + if (result == MatchStatus.ABSTAIN && this.matchDefault) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Matched document with default matcher: " + map); } + callback.process(properties, map); + return true; } - return true; + + this.logger.debug("Unmatched document"); + return false; } private void assignProperties(Properties properties, Map input, @@ -300,7 +303,21 @@ public class YamlProcessor { * Status returned from {@link DocumentMatcher#matches(Properties)} */ 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. diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index 924e4d76f0e..6477d902100 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -1,3 +1,8 @@ +# ProperySource Loaders +org.springframework.boot.env.PropertySourceLoader=\ +org.springframework.boot.env.PropertiesPropertySourceLoader,\ +org.springframework.boot.env.YamlPropertySourceLoader + # Run Participants org.springframework.boot.SpringApplicationRunParticipant=\ org.springframework.boot.event.EventPublishingRunParticipant diff --git a/spring-boot/src/test/java/org/springframework/boot/ReproTests.java b/spring-boot/src/test/java/org/springframework/boot/ReproTests.java index c347c0d3ffe..1323723b443 100644 --- a/spring-boot/src/test/java/org/springframework/boot/ReproTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/ReproTests.java @@ -43,6 +43,36 @@ public class ReproTests { 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 public static class Config { diff --git a/spring-boot/src/test/java/org/springframework/boot/config/ConfigFileApplicationListenerTests.java b/spring-boot/src/test/java/org/springframework/boot/config/ConfigFileApplicationListenerTests.java index 5a6bd80657b..d6e69f3ad91 100644 --- a/spring-boot/src/test/java/org/springframework/boot/config/ConfigFileApplicationListenerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/config/ConfigFileApplicationListenerTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.config; import java.util.Arrays; -import java.util.List; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -27,6 +26,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; +import org.springframework.boot.config.ConfigFileApplicationListener.ConfigurationPropertySources; import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.ConfigurableApplicationContext; @@ -34,10 +34,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.PropertySource; 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.StandardEnvironment; -import org.springframework.core.io.Resource; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; @@ -72,7 +71,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadPropertiesFile() throws Exception { - this.initializer.setNames("testproperties"); + this.initializer.setSearchNames("testproperties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("frompropertiesfile")); @@ -108,7 +107,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadTwoPropertiesFiles() throws Exception { - this.initializer.setNames("moreproperties,testproperties"); + this.initializer.setSearchNames("moreproperties,testproperties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("frommorepropertiesfile")); @@ -116,7 +115,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadYamlFile() throws Exception { - this.initializer.setNames("testyaml"); + this.initializer.setSearchNames("testyaml"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("fromyamlfile")); @@ -128,7 +127,7 @@ public class ConfigFileApplicationListenerTests { public void commandLineWins() throws Exception { this.environment.getPropertySources().addFirst( new SimpleCommandLinePropertySource("--my.property=fromcommandline")); - this.initializer.setNames("testproperties"); + this.initializer.setSearchNames("testproperties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("fromcommandline")); @@ -137,7 +136,7 @@ public class ConfigFileApplicationListenerTests { @Test public void systemPropertyWins() throws Exception { System.setProperty("my.property", "fromsystem"); - this.initializer.setNames("testproperties"); + this.initializer.setSearchNames("testproperties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("fromsystem")); @@ -145,7 +144,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadPropertiesThenProfileProperties() throws Exception { - this.initializer.setNames("enableprofile"); + this.initializer.setSearchNames("enableprofile"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("fromprofilepropertiesfile")); @@ -153,7 +152,7 @@ public class ConfigFileApplicationListenerTests { @Test public void profilePropertiesUsedInPlaceholders() throws Exception { - this.initializer.setNames("enableprofile"); + this.initializer.setSearchNames("enableprofile"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("one.more"); assertThat(property, equalTo("fromprofilepropertiesfile")); @@ -161,7 +160,7 @@ public class ConfigFileApplicationListenerTests { @Test public void yamlProfiles() throws Exception { - this.initializer.setNames("testprofiles"); + this.initializer.setSearchNames("testprofiles"); this.environment.setActiveProfiles("dev"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); @@ -172,7 +171,7 @@ public class ConfigFileApplicationListenerTests { @Test public void yamlSetsProfiles() throws Exception { - this.initializer.setNames("testsetprofiles"); + this.initializer.setSearchNames("testsetprofiles"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(Arrays.asList(this.environment.getActiveProfiles()), contains("dev")); @@ -183,7 +182,7 @@ public class ConfigFileApplicationListenerTests { public void yamlProfileCanBeChanged() throws Exception { EnvironmentTestUtils.addEnvironment(this.environment, "spring.profiles.active:prod"); - this.initializer.setNames("testsetprofiles"); + this.initializer.setSearchNames("testsetprofiles"); this.initializer.onApplicationEvent(this.event); assertThat(this.environment.getActiveProfiles(), equalTo(new String[] { "prod" })); } @@ -206,10 +205,11 @@ public class ConfigFileApplicationListenerTests { this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); 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 - assertThat(this.environment, containsProperySource("classpath:" - + "/application.properties")); + assertThat(this.environment, containsProperySource("applicationConfig: " + + "class path resource [application.properties]")); assertThat(this.environment.getProperty("foo"), equalTo("bucket")); } @@ -219,35 +219,8 @@ public class ConfigFileApplicationListenerTests { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" + location); this.initializer.onApplicationEvent(this.event); - assertThat(this.environment, containsProperySource(location)); - } - - @Test - public void unsupportedResource() throws Exception { - this.initializer - .setPropertySourceLoadersFactory(new PropertySourceLoadersFactory() { - @Override - public List getLoaders(Environment environment) { - return Arrays - . 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); + assertThat(this.environment, containsProperySource("applicationConfig: " + + "URL [" + location + "]")); } @Test @@ -256,7 +229,8 @@ public class ConfigFileApplicationListenerTests { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" + location); this.initializer.onApplicationEvent(this.event); - assertThat(this.environment, containsProperySource("file:" + location)); + assertThat(this.environment, containsProperySource("applicationConfig: " + + "URL [file:" + location + "]")); } @Test @@ -266,8 +240,8 @@ public class ConfigFileApplicationListenerTests { ConfigurableApplicationContext context = application.run(); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("fromspecificlocation")); - assertThat(context.getEnvironment(), containsProperySource("classpath:" - + "/specificlocation.properties")); + assertThat(context.getEnvironment(), containsProperySource("class path resource " + + "[specificlocation.properties]")); context.close(); } @@ -282,8 +256,8 @@ public class ConfigFileApplicationListenerTests { ConfigurableApplicationContext context = application.run(); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("fromspecificlocation")); - assertThat(context.getEnvironment(), containsProperySource("classpath:" - + "/specificlocation.properties")); + assertThat(context.getEnvironment(), containsProperySource("class path resource " + + "[specificlocation.properties]")); context.close(); } @@ -295,10 +269,7 @@ public class ConfigFileApplicationListenerTests { ConfigurableApplicationContext context = application.run(); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("fromspecificlocation")); - // In this case "foo" should be the specificlocation.properties source, but Spring - // will have shifted it to the back of the line. - assertThat(context.getEnvironment().getPropertySources().get("boot.foo"), - notNullValue()); + assertThat(context.getEnvironment(), containsProperySource("foo")); context.close(); } @@ -311,10 +282,10 @@ public class ConfigFileApplicationListenerTests { .run("--spring.profiles.active=myprofile"); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("frompropertiesfile")); - assertThat(context.getEnvironment(), containsProperySource("classpath:" - + "/enableprofile.properties")); - assertThat(context.getEnvironment(), not(containsProperySource("classpath:" - + "/enableprofile-myprofile.properties"))); + assertThat(context.getEnvironment(), containsProperySource("class path resource " + + "[enableprofile.properties]")); + assertThat(context.getEnvironment(), not(containsProperySource("classpath:/" + + "enableprofile-myprofile.properties"))); context.close(); } @@ -339,8 +310,8 @@ public class ConfigFileApplicationListenerTests { ConfigurableApplicationContext context = application.run(); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("frommorepropertiesfile")); - assertThat(context.getEnvironment(), - containsProperySource("classpath:/specificlocation.properties")); + assertThat(context.getEnvironment(), containsProperySource("class path resource " + + "[specificlocation.properties]")); context.close(); } @@ -352,12 +323,7 @@ public class ConfigFileApplicationListenerTests { ConfigurableApplicationContext context = application.run(); String property = context.getEnvironment().getProperty("my.property"); assertThat(property, equalTo("frommorepropertiesfile")); - // foo is there but it is a dead rubber because the individual sources get higher - // priority (and are named after the resource locations) - assertThat(context.getEnvironment().getPropertySources().get("foo"), - notNullValue()); - assertThat(context.getEnvironment(), - containsProperySource("classpath:/specificlocation.properties")); + assertThat(context.getEnvironment(), containsProperySource("foo")); context.close(); } @@ -377,7 +343,6 @@ public class ConfigFileApplicationListenerTests { private static Matcher containsProperySource( final String sourceName) { return new TypeSafeDiagnosingMatcher() { - @Override public void describeTo(Description description) { description.appendText("environment containing property source ") @@ -387,9 +352,12 @@ public class ConfigFileApplicationListenerTests { @Override protected boolean matchesSafely(ConfigurableEnvironment item, Description mismatchDescription) { - mismatchDescription.appendText("Not matched against: ").appendValue( + MutablePropertySources sources = new MutablePropertySources( item.getPropertySources()); - return item.getPropertySources().contains(sourceName); + ConfigurationPropertySources.finishAndRelocate(sources); + mismatchDescription.appendText("Not matched against: ").appendValue( + sources); + return sources.contains(sourceName); } }; } diff --git a/spring-boot/src/test/java/org/springframework/boot/yaml/YamlProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/yaml/YamlProcessorTests.java index a22fe971812..14332917bcf 100644 --- a/spring-boot/src/test/java/org/springframework/boot/yaml/YamlProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/yaml/YamlProcessorTests.java @@ -36,7 +36,8 @@ import static org.junit.Assert.assertEquals; */ public class YamlProcessorTests { - private final YamlProcessor processor = new YamlProcessor(); + private final YamlProcessor processor = new YamlProcessor() { + }; @Rule public ExpectedException exception = ExpectedException.none(); diff --git a/spring-boot/src/test/java/org/springframework/boot/yaml/YamlPropertiesFactoryBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/yaml/YamlPropertiesFactoryBeanTests.java index 2cb6d1b9fbd..22393a06939 100644 --- a/spring-boot/src/test/java/org/springframework/boot/yaml/YamlPropertiesFactoryBeanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/yaml/YamlPropertiesFactoryBeanTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.yaml; -import java.util.Arrays; import java.util.Map; import java.util.Properties; @@ -33,7 +32,9 @@ import org.springframework.core.io.Resource; import org.yaml.snakeyaml.Yaml; 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}. @@ -51,8 +52,8 @@ public class YamlPropertiesFactoryBeanTests { factory.setResources(new Resource[] { new ByteArrayResource( "foo: bar\nspam:\n foo: baz".getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bar", properties.get("foo")); - assertEquals("baz", properties.get("spam.foo")); + assertThat(properties.getProperty("foo"), equalTo("bar")); + assertThat(properties.getProperty("spam.foo"), equalTo("baz")); } @Test @@ -72,31 +73,29 @@ public class YamlPropertiesFactoryBeanTests { new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()), new ByteArrayResource("foo:\n bar: spam".getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bar", properties.get("foo")); - assertEquals("baz", properties.get("spam.foo")); - assertEquals("spam", properties.get("foo.bar")); + assertThat(properties.getProperty("foo"), equalTo("bar")); + assertThat(properties.getProperty("spam.foo"), equalTo("baz")); + assertThat(properties.getProperty("foo.bar"), equalTo("spam")); } @Test - @Ignore - // We can't fail on duplicate keys because the Map is created by the YAML library + @Ignore("We can't fail on duplicate keys because the Map is created by the YAML library") public void testLoadResourcesWithInternalOverride() throws Exception { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new Resource[] { new ByteArrayResource( "foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bar", properties.get("foo")); + assertThat(properties.getProperty("foo"), equalTo("bar")); } @Test - @Ignore - // We can't fail on duplicate keys because the Map is created by the YAML library + @Ignore("We can't fail on duplicate keys because the Map is created by the YAML library") public void testLoadResourcesWithNestedInternalOverride() throws Exception { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new Resource[] { new ByteArrayResource( "foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes()) }); Properties properties = factory.getObject(); - assertEquals("spam", properties.get("foo.bar")); + assertThat(properties.getProperty("foo.bar"), equalTo("spam")); } @Test @@ -105,8 +104,8 @@ public class YamlPropertiesFactoryBeanTests { factory.setResources(new Resource[] { new ByteArrayResource( "foo: bar\nspam: baz\n---\nfoo: bag".getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bag", properties.get("foo")); - assertEquals("baz", properties.get("spam")); + assertThat(properties.getProperty("foo"), equalTo("bag")); + assertThat(properties.getProperty("spam"), equalTo("baz")); } @Test @@ -114,17 +113,16 @@ public class YamlPropertiesFactoryBeanTests { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(new Resource[] { new ByteArrayResource( "foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); - factory.setDocumentMatchers(Arrays - . asList(new DocumentMatcher() { - @Override - public MatchStatus matches(Properties properties) { - return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND - : MatchStatus.NOT_FOUND; - } - })); + factory.setDocumentMatchers(new DocumentMatcher() { + @Override + public MatchStatus matches(Properties properties) { + return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND + : MatchStatus.NOT_FOUND; + } + }); Properties properties = factory.getObject(); - assertEquals("bag", properties.get("foo")); - assertEquals("bad", properties.get("spam")); + assertThat(properties.getProperty("foo"), equalTo("bag")); + assertThat(properties.getProperty("spam"), equalTo("bad")); } @Test @@ -133,21 +131,42 @@ public class YamlPropertiesFactoryBeanTests { factory.setMatchDefault(true); factory.setResources(new Resource[] { new ByteArrayResource( "one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) }); - factory.setDocumentMatchers(Arrays - . asList(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; - } - })); + 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(); - assertEquals("bag", properties.get("foo")); - assertEquals("bad", properties.get("spam")); - assertEquals("two", properties.get("one")); + assertThat(properties.getProperty("foo"), equalTo("bag")); + assertThat(properties.getProperty("spam"), equalTo("bad")); + 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 @@ -156,21 +175,20 @@ public class YamlPropertiesFactoryBeanTests { factory.setMatchDefault(true); factory.setResources(new Resource[] { new ByteArrayResource( "one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes()) }); - factory.setDocumentMatchers(Arrays - . asList(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; - } - })); + 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(); - assertEquals("bag", properties.get("foo")); - assertEquals("bad", properties.get("spam")); - assertEquals("two", properties.get("one")); + assertThat(properties.getProperty("foo"), equalTo("bag")); + assertThat(properties.getProperty("spam"), equalTo("bad")); + assertThat(properties.getProperty("one"), equalTo("two")); } @Test @@ -179,7 +197,7 @@ public class YamlPropertiesFactoryBeanTests { factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE); factory.setResources(new Resource[] { new ClassPathResource("no-such-file.yml") }); Properties properties = factory.getObject(); - assertEquals(0, properties.size()); + assertThat(properties.size(), equalTo(0)); } @Test @@ -188,8 +206,8 @@ public class YamlPropertiesFactoryBeanTests { factory.setResources(new Resource[] { new ByteArrayResource("foo: bar\nspam:" .getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bar", properties.get("foo")); - assertEquals("", properties.get("spam")); + assertThat(properties.getProperty("foo"), equalTo("bar")); + assertThat(properties.getProperty("spam"), equalTo("")); } @Test @@ -198,8 +216,8 @@ public class YamlPropertiesFactoryBeanTests { factory.setResources(new Resource[] { new ByteArrayResource("foo:\n- bar\n- baz" .getBytes()) }); Properties properties = factory.getObject(); - assertEquals("bar", properties.get("foo[0]")); - assertEquals("baz", properties.get("foo[1]")); + assertThat(properties.getProperty("foo[0]"), equalTo("bar")); + assertThat(properties.getProperty("foo[1]"), equalTo("baz")); } @Test @@ -209,10 +227,10 @@ public class YamlPropertiesFactoryBeanTests { "foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four" .getBytes()) }); Properties properties = factory.getObject(); - assertEquals("crap", properties.get("foo[0].bar.spam")); - assertEquals("baz", properties.get("foo[1]")); - assertEquals("two", properties.get("foo[2].one")); - assertEquals("four", properties.get("foo[2].three")); + assertThat(properties.getProperty("foo[0].bar.spam"), equalTo("crap")); + assertThat(properties.getProperty("foo[1]"), equalTo("baz")); + assertThat(properties.getProperty("foo[2].one"), equalTo("two")); + assertThat(properties.getProperty("foo[2].three"), equalTo("four")); } @SuppressWarnings("unchecked") @@ -220,8 +238,9 @@ public class YamlPropertiesFactoryBeanTests { public void testYaml() { Yaml yaml = new Yaml(); Map map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class); - assertEquals("bar", map.get("foo")); - assertEquals("baz", ((Map) map.get("spam")).get("foo")); + assertThat(map.get("foo"), equalTo((Object) "bar")); + assertThat(((Map) map.get("spam")).get("foo"), + equalTo((Object) "baz")); } } diff --git a/spring-boot/src/test/resources/activeprofilerepro.yml b/spring-boot/src/test/resources/activeprofilerepro.yml new file mode 100644 index 00000000000..3871896a3b7 --- /dev/null +++ b/spring-boot/src/test/resources/activeprofilerepro.yml @@ -0,0 +1,11 @@ +spring.profiles.active: B +--- +spring.profiles: A +version: A +--- +spring.profiles: B +version: B +--- +spring.profiles: C +version: C +---