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,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);
}
}
\ No newline at end of file
...@@ -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));
...@@ -55,4 +62,4 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher { ...@@ -55,4 +62,4 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher {
return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties); return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties);
} }
} }
\ No newline at end of file
...@@ -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,43 +195,42 @@ public class YamlProcessor { ...@@ -191,43 +195,42 @@ 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); result = MatchStatus.getMostSpecific(match, result);
result = MatchStatus.getMostSpecific(match, result); if (match == MatchStatus.FOUND) {
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) {
if (this.logger.isDebugEnabled()) { 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); 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<String, Object> input, private void assignProperties(Properties properties, Map<String, Object> input,
...@@ -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 {
......
...@@ -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();
......
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