001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.context.properties.source;
018
019import java.util.Collections;
020import java.util.stream.Stream;
021
022import org.springframework.core.env.ConfigurableEnvironment;
023import org.springframework.core.env.Environment;
024import org.springframework.core.env.MutablePropertySources;
025import org.springframework.core.env.PropertySource;
026import org.springframework.core.env.PropertySource.StubPropertySource;
027import org.springframework.core.env.PropertySources;
028import org.springframework.core.env.PropertySourcesPropertyResolver;
029import org.springframework.util.Assert;
030
031/**
032 * Provides access to {@link ConfigurationPropertySource ConfigurationPropertySources}.
033 *
034 * @author Phillip Webb
035 * @since 2.0.0
036 */
037public final class ConfigurationPropertySources {
038
039        /**
040         * The name of the {@link PropertySource} {@link #adapt adapter}.
041         */
042        private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
043
044        private ConfigurationPropertySources() {
045        }
046
047        /**
048         * Determines if the specific {@link PropertySource} is the
049         * {@link ConfigurationPropertySource} that was {@link #attach(Environment) attached}
050         * to the {@link Environment}.
051         * @param propertySource the property source to test
052         * @return {@code true} if this is the attached {@link ConfigurationPropertySource}
053         */
054        public static boolean isAttachedConfigurationPropertySource(
055                        PropertySource<?> propertySource) {
056                return ATTACHED_PROPERTY_SOURCE_NAME.equals(propertySource.getName());
057        }
058
059        /**
060         * Attach a {@link ConfigurationPropertySource} support to the specified
061         * {@link Environment}. Adapts each {@link PropertySource} managed by the environment
062         * to a {@link ConfigurationPropertySource} and allows classic
063         * {@link PropertySourcesPropertyResolver} calls to resolve using
064         * {@link ConfigurationPropertyName configuration property names}.
065         * <p>
066         * The attached resolver will dynamically track any additions or removals from the
067         * underlying {@link Environment} property sources.
068         * @param environment the source environment (must be an instance of
069         * {@link ConfigurableEnvironment})
070         * @see #get(Environment)
071         */
072        public static void attach(Environment environment) {
073                Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
074                MutablePropertySources sources = ((ConfigurableEnvironment) environment)
075                                .getPropertySources();
076                PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
077                if (attached != null && attached.getSource() != sources) {
078                        sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
079                        attached = null;
080                }
081                if (attached == null) {
082                        sources.addFirst(new ConfigurationPropertySourcesPropertySource(
083                                        ATTACHED_PROPERTY_SOURCE_NAME,
084                                        new SpringConfigurationPropertySources(sources)));
085                }
086        }
087
088        /**
089         * Return a set of {@link ConfigurationPropertySource} instances that have previously
090         * been {@link #attach(Environment) attached} to the {@link Environment}.
091         * @param environment the source environment (must be an instance of
092         * {@link ConfigurableEnvironment})
093         * @return an iterable set of configuration property sources
094         * @throws IllegalStateException if not configuration property sources have been
095         * attached
096         */
097        public static Iterable<ConfigurationPropertySource> get(Environment environment) {
098                Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
099                MutablePropertySources sources = ((ConfigurableEnvironment) environment)
100                                .getPropertySources();
101                ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource) sources
102                                .get(ATTACHED_PROPERTY_SOURCE_NAME);
103                if (attached == null) {
104                        return from(sources);
105                }
106                return attached.getSource();
107        }
108
109        /**
110         * Return {@link Iterable} containing a single new {@link ConfigurationPropertySource}
111         * adapted from the given Spring {@link PropertySource}.
112         * @param source the Spring property source to adapt
113         * @return an {@link Iterable} containing a single newly adapted
114         * {@link SpringConfigurationPropertySource}
115         */
116        public static Iterable<ConfigurationPropertySource> from(PropertySource<?> source) {
117                return Collections.singleton(SpringConfigurationPropertySource.from(source));
118        }
119
120        /**
121         * Return {@link Iterable} containing new {@link ConfigurationPropertySource}
122         * instances adapted from the given Spring {@link PropertySource PropertySources}.
123         * <p>
124         * This method will flatten any nested property sources and will filter all
125         * {@link StubPropertySource stub property sources}. Updates to the underlying source,
126         * identified by changes in the sources returned by its iterator, will be
127         * automatically tracked. The underlying source should be thread safe, for example a
128         * {@link MutablePropertySources}
129         * @param sources the Spring property sources to adapt
130         * @return an {@link Iterable} containing newly adapted
131         * {@link SpringConfigurationPropertySource} instances
132         */
133        public static Iterable<ConfigurationPropertySource> from(
134                        Iterable<PropertySource<?>> sources) {
135                return new SpringConfigurationPropertySources(sources);
136        }
137
138        private static Stream<PropertySource<?>> streamPropertySources(
139                        PropertySources sources) {
140                return sources.stream().flatMap(ConfigurationPropertySources::flatten)
141                                .filter(ConfigurationPropertySources::isIncluded);
142        }
143
144        private static Stream<PropertySource<?>> flatten(PropertySource<?> source) {
145                if (source.getSource() instanceof ConfigurableEnvironment) {
146                        return streamPropertySources(
147                                        ((ConfigurableEnvironment) source.getSource()).getPropertySources());
148                }
149                return Stream.of(source);
150        }
151
152        private static boolean isIncluded(PropertySource<?> source) {
153                return !(source instanceof StubPropertySource)
154                                && !(source instanceof ConfigurationPropertySourcesPropertySource);
155        }
156
157}