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 new file mode 100644 index 0000000000..4dc7b97e55 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/config/DefaultPropertySourceLoadersFactory.java @@ -0,0 +1,45 @@ +/* + * 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/PropertySourceLoadersFactory.java b/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoadersFactory.java new file mode 100644 index 0000000000..456d8f97f1 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/config/PropertySourceLoadersFactory.java @@ -0,0 +1,39 @@ +/* + * 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/context/listener/ConfigFileApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/context/listener/ConfigFileApplicationListener.java index 1a128d4596..56546d1de9 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/listener/ConfigFileApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/listener/ConfigFileApplicationListener.java @@ -21,6 +21,7 @@ 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.List; import java.util.Map; @@ -33,26 +34,24 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationEnvironmentAvailableEvent; import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; -import org.springframework.boot.config.PropertiesPropertySourceLoader; +import org.springframework.boot.config.DefaultPropertySourceLoadersFactory; import org.springframework.boot.config.PropertySourceLoader; -import org.springframework.boot.config.YamlPropertySourceLoader; +import org.springframework.boot.config.PropertySourceLoadersFactory; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.PropertySources; import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAttributes; +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.core.type.AnnotationMetadata; -import org.springframework.core.type.StandardAnnotationMetadata; -import org.springframework.util.ClassUtils; import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; @@ -66,16 +65,13 @@ import org.springframework.util.StringUtils; *
  • classpath:config/
  • *
  • file:./config/:
  • * - * *

    * Alternative locations and names can be specified using * {@link #setSearchLocations(String[])} and {@link #setNames(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 @@ -87,6 +83,8 @@ import org.springframework.util.StringUtils; public class ConfigFileApplicationListener implements ApplicationListener, Ordered { + private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; + private static final String LOCATION_VARIABLE = "${spring.config.location}"; private String[] searchLocations = new String[] { "classpath:", "file:./", @@ -96,13 +94,13 @@ public class ConfigFileApplicationListener implements private int order = Integer.MIN_VALUE + 10; - private final Map> cached = new HashMap>(); - private final ConversionService conversionService = new DefaultConversionService(); - private final PropertySourceAnnotations propertySourceAnnotations = new PropertySourceAnnotations(); + private final Map> cache = new HashMap>(); + + private final PropertySourceAnnotations annotations = new PropertySourceAnnotations(); - private PropertySourceLoaderFactory propertySourceLoaderFactory = new DefaultPropertySourceLoaderFactory(); + private PropertySourceLoadersFactory propertySourceLoadersFactory = new DefaultPropertySourceLoadersFactory(); /** * Binds the early {@link Environment} to the {@link SpringApplication}. This makes it @@ -113,81 +111,63 @@ public class ConfigFileApplicationListener implements */ @Override public void onApplicationEvent(SpringApplicationEnvironmentAvailableEvent event) { - Environment created = event.getEnvironment(); - if (created instanceof ConfigurableEnvironment) { - SpringApplication springApplication = event.getSpringApplication(); - extractPropertySources(springApplication.getSources()); - ConfigurableEnvironment environment = (ConfigurableEnvironment) created; - load(environment, new DefaultResourceLoader()); - environment.getPropertySources().addAfter( - StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, - new RandomValuePropertySource("random")); - int before = 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); - int after = springApplication.getSources().size(); - if (after > before) { - // Do it again in case there are new @PropertySources - onApplicationEvent(event); - } + Environment environment = event.getEnvironment(); + if (environment instanceof ConfigurableEnvironment) { + configure((ConfigurableEnvironment) environment, event.getSpringApplication()); } } - private void extractPropertySources(Set sources) { - for (Object source : sources) { - if (source instanceof Class) { - Class type = (Class) source; - for (AnnotationAttributes propertySource : attributesForRepeatable( - new StandardAnnotationMetadata(type), PropertySources.class, - org.springframework.context.annotation.PropertySource.class)) { - this.propertySourceAnnotations.add(type, - propertySource.getStringArray("value"), - propertySource.getBoolean("ignoreResourceNotFound"), - propertySource.getString("name")); - } - } + 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); } } - @SuppressWarnings("unchecked") - static Set attributesForRepeatable(AnnotationMetadata metadata, - Class containerClass, Class annotationClass) { - Set result = new LinkedHashSet(); + private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + + LoadCandidates candidates = new LoadCandidates(environment, resourceLoader); + PropertySource defaultProperties = environment.getPropertySources().remove( + "defaultProperties"); - addAttributesIfNotNull(result, - metadata.getAnnotationAttributes(annotationClass.getName(), false)); + String firstPropertySourceName = loadInitial(environment, resourceLoader, + candidates); - Map container = metadata.getAnnotationAttributes( - containerClass.getName(), false); - if (container != null && container.containsKey("value")) { - for (Map containedAttributes : (Map[]) container - .get("value")) { - addAttributesIfNotNull(result, containedAttributes); + if (environment.containsProperty(ACTIVE_PROFILES_PROPERTY)) { + for (String activeProfile : StringUtils.commaDelimitedListToSet(environment + .getProperty(ACTIVE_PROFILES_PROPERTY).toString())) { + environment.addActiveProfile(activeProfile); } } - return Collections.unmodifiableSet(result); - } - private static void addAttributesIfNotNull(Set result, - Map attributes) { - if (attributes != null) { - result.add(AnnotationAttributes.fromMap(attributes)); + // Second load for specific profiles + loadAgain(environment, resourceLoader, candidates, firstPropertySourceName); + + if (defaultProperties != null) { + environment.getPropertySources().addLast(defaultProperties); } } - private void load(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { - - List candidates = getCandidateLocations(environment, resourceLoader); - Collections.reverse(candidates); - PropertySource removed = environment.getPropertySources().remove( - "defaultProperties"); - - String first = null; + 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 @@ -200,87 +180,51 @@ public class ConfigFileApplicationListener implements path = StringUtils.cleanPath(path); } - PropertySource source = load(environment, resourceLoader, path, null); + PropertySource source = loadPropertySource(environment, + resourceLoader, path, null); if (source != null) { - if (first == null) { - first = source.getName(); + if (firstSourceName == null) { + firstSourceName = source.getName(); } environment.getPropertySources().addLast(source); } - - } - } - - if (environment.containsProperty("spring.profiles.active")) { - Set profiles = StringUtils.commaDelimitedListToSet(environment - .getProperty("spring.profiles.active").toString()); - for (String active : profiles) { - // allow document with no profile to set the active one - environment.addActiveProfile(active); } - } + return firstSourceName; + } - // Second load for specific profiles + private void loadAgain(ConfigurableEnvironment environment, + ResourceLoader resourceLoader, LoadCandidates candidates, + String firstPropertySourceName) { for (String profile : environment.getActiveProfiles()) { for (String candidate : candidates) { - PropertySource source = load(environment, resourceLoader, candidate, - profile); - if (source != null) { - if (first != null) { - // Originals go at the end so they don't override the specific - // profiles - environment.getPropertySources().addBefore(first, source); - } - else { - environment.getPropertySources().addLast(source); - } - } + PropertySource source = loadPropertySource(environment, + resourceLoader, candidate, profile); + addBeforeOrLast(environment, firstPropertySourceName, source); } } - - if (removed != null) { - environment.getPropertySources().addLast(removed); - } } - private List getCandidateLocations(ConfigurableEnvironment environment, - ResourceLoader resourceLoader) { - Set candidates = new LinkedHashSet(); - for (String searchLocation : this.searchLocations) { - for (String extension : new String[] { ".properties", ".yml" }) { - for (String name : StringUtils - .commaDelimitedListToStringArray(environment - .resolvePlaceholders(this.names))) { - String location = searchLocation + name + extension; - candidates.add(location); - } + 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); } - } - 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). - */ - for (String location : this.propertySourceAnnotations.locations()) { - Resource resource = resourceLoader.getResource(location); - if (!this.propertySourceAnnotations.ignoreResourceNotFound(location) - && !resource.exists()) { - throw new IllegalStateException("Resource not found: " + location); + else { + propertySources.addLast(source); } - candidates.add(location); } - return new ArrayList(candidates); } - private PropertySource load(ConfigurableEnvironment environment, + private PropertySource loadPropertySource(ConfigurableEnvironment environment, ResourceLoader resourceLoader, String location, String profile) { - String suffix = "." + StringUtils.getFilenameExtension(location); - Class type = this.propertySourceAnnotations.configuration(location); + Class type = this.annotations.configuration(location); + String suffix = "." + StringUtils.getFilenameExtension(location); if (StringUtils.hasLength(profile)) { location = location.replace(suffix, "-" + profile + suffix); } @@ -289,71 +233,56 @@ public class ConfigFileApplicationListener implements return null; } - List loaders = this.propertySourceLoaderFactory - .getLoaders(environment); - Resource resource = resourceLoader.getResource(location); - String name = this.propertySourceAnnotations.name(location); - if (name == null) { - name = location; - } - PropertySource propertySource = getPropertySource(name, resource, profile, - loaders); - if (propertySource == null) { - return null; - } - return propertySource; + String name = this.annotations.name(location); + name = (name != null ? name : location); + return getPropertySource(environment, name, resource, profile); } 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.propertySourceAnnotations.locations().contains(location)) { + && !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(); - if (after == before) { - // The configuration class was @Conditional and excluded - return true; - } - return false; + + // Return if the configuration class was @Conditional and excluded + return (after == before); } - private PropertySource getPropertySource(String name, Resource resource, - String profile, List loaders) { + 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.cached.containsKey(key)) { - return this.cached.get(key); + if (this.cache.containsKey(key)) { + return this.cache.get(key); } - boolean satisfied = true; - for (PropertySourceLoader loader : loaders) { + for (PropertySourceLoader loader : this.propertySourceLoadersFactory + .getLoaders(environment)) { if (loader.supports(resource)) { PropertySource propertySource = loader.load(name, resource); - this.cached.put(key, propertySource); + this.cache.put(key, propertySource); return propertySource; } - else { - satisfied = false; - } } - if (!satisfied) { - throw new IllegalStateException( - "No supported loader found for configuration resource: " + resource); - } - return null; + throw new IllegalStateException("No supported loader found for " + + "configuration resource: " + resource); } public void setOrder(int order) { @@ -381,13 +310,75 @@ public class ConfigFileApplicationListener implements } /** - * @param propertySourceLoaderFactory the factory to set + * Set the {@link PropertySourceLoadersFactory} that will be used to create + * {@link PropertySourceLoader}s. + */ + public void setPropertySourceLoadersFactory( + PropertySourceLoadersFactory propertySourceLoaderFactory) { + this.propertySourceLoadersFactory = propertySourceLoaderFactory; + } + + /** + * Provides {@link Iterable} access to candidate property sources. */ - public void setPropertySourceLoaderFactory( - PropertySourceLoaderFactory propertySourceLoaderFactory) { - this.propertySourceLoaderFactory = propertySourceLoaderFactory; + 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(resourceLoader, candidates); + this.candidates = new ArrayList(candidates); + Collections.reverse(this.candidates); + } + + private void addLoadCandidatesFromSearchLocations( + ConfigurableEnvironment environment, Set candidates) { + for (String location : ConfigFileApplicationListener.this.searchLocations) { + for (String extension : new String[] { ".properties", ".yml" }) { + for (String name : StringUtils + .commaDelimitedListToStringArray(environment + .resolvePlaceholders(ConfigFileApplicationListener.this.names))) { + candidates.add(location + name + extension); + } + } + } + } + + private void addLoadCandidatesFromAnnotations(ResourceLoader resourceLoader, + Set candidates) { + for (String location : ConfigFileApplicationListener.this.annotations + .getLocations()) { + Resource resource = resourceLoader.getResource(location); + if (!ConfigFileApplicationListener.this.annotations + .ignoreResourceNotFound(location) && !resource.exists()) { + throw new IllegalStateException("Resource not found: " + location); + } + candidates.add(location); + } + } + + @Override + public Iterator iterator() { + return this.candidates.iterator(); + } + } + /** + * {@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"}. + */ private static class RandomValuePropertySource extends PropertySource { public RandomValuePropertySource(String name) { @@ -412,6 +403,10 @@ public class ConfigFileApplicationListener implements } + /** + * Holds details collected from + * {@link org.springframework.context.annotation.PropertySource} annotations. + */ private static class PropertySourceAnnotations { private final Collection locations = new LinkedHashSet(); @@ -422,16 +417,30 @@ public class ConfigFileApplicationListener implements private final Map ignores = new HashMap(); - public void add(Class source, String[] locations, - boolean ignoreResourceNotFound, String name) { - this.locations.addAll(Arrays.asList(locations)); - if (StringUtils.hasText(name)) { - for (String location : locations) { - this.names.put(location, name); + public void addFromSource(Object source) { + if (source instanceof Class) { + addFromSource((Class) source); + } + } + + 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 void add(Class source, + org.springframework.context.annotation.PropertySource annotation) { + this.locations.addAll(Arrays.asList(annotation.value())); + if (StringUtils.hasText(annotation.name())) { + for (String location : annotation.value()) { + this.names.put(location, annotation.name()); } } - for (String location : locations) { - boolean reallyIgnore = ignoreResourceNotFound; + 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); @@ -446,8 +455,7 @@ public class ConfigFileApplicationListener implements } public boolean ignoreResourceNotFound(String location) { - return this.ignores.containsKey(location) ? this.ignores.get(location) - : false; + return Boolean.TRUE.equals(this.ignores.get(location)); } public String name(String location) { @@ -459,29 +467,9 @@ public class ConfigFileApplicationListener implements return "boot." + name; } - public Collection locations() { + public Collection getLocations() { return this.locations; } } - public static interface PropertySourceLoaderFactory { - List getLoaders(Environment environment); - } - - private static class DefaultPropertySourceLoaderFactory implements - PropertySourceLoaderFactory { - - @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/test/java/org/springframework/boot/context/listener/ConfigFileApplicationListenerTests.java b/spring-boot/src/test/java/org/springframework/boot/context/listener/ConfigFileApplicationListenerTests.java index 92e0834c9d..0cbf802938 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/listener/ConfigFileApplicationListenerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/listener/ConfigFileApplicationListenerTests.java @@ -26,7 +26,7 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplicationEnvironmentAvailableEvent; import org.springframework.boot.config.PropertySourceLoader; -import org.springframework.boot.context.listener.ConfigFileApplicationListener.PropertySourceLoaderFactory; +import org.springframework.boot.config.PropertySourceLoadersFactory; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; @@ -228,7 +228,7 @@ public class ConfigFileApplicationListenerTests { @Test public void unsupportedResource() throws Exception { this.initializer - .setPropertySourceLoaderFactory(new PropertySourceLoaderFactory() { + .setPropertySourceLoadersFactory(new PropertySourceLoadersFactory() { @Override public List getLoaders(Environment environment) { return Arrays