001/*
002 * Copyright 2002-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 *      https://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.context.support;
018
019import java.io.IOException;
020import java.util.Properties;
021
022import org.springframework.beans.BeansException;
023import org.springframework.beans.factory.BeanInitializationException;
024import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
025import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
026import org.springframework.context.EnvironmentAware;
027import org.springframework.core.env.ConfigurablePropertyResolver;
028import org.springframework.core.env.Environment;
029import org.springframework.core.env.MutablePropertySources;
030import org.springframework.core.env.PropertiesPropertySource;
031import org.springframework.core.env.PropertySource;
032import org.springframework.core.env.PropertySources;
033import org.springframework.core.env.PropertySourcesPropertyResolver;
034import org.springframework.lang.Nullable;
035import org.springframework.util.Assert;
036import org.springframework.util.StringValueResolver;
037
038/**
039 * Specialization of {@link PlaceholderConfigurerSupport} that resolves ${...} placeholders
040 * within bean definition property values and {@code @Value} annotations against the current
041 * Spring {@link Environment} and its set of {@link PropertySources}.
042 *
043 * <p>This class is designed as a general replacement for {@code PropertyPlaceholderConfigurer}.
044 * It is used by default to support the {@code property-placeholder} element in working against
045 * the spring-context-3.1 or higher XSD; whereas, spring-context versions &lt;= 3.0 default to
046 * {@code PropertyPlaceholderConfigurer} to ensure backward compatibility. See the spring-context
047 * XSD documentation for complete details.
048 *
049 * <p>Any local properties (e.g. those added via {@link #setProperties}, {@link #setLocations}
050 * et al.) are added as a {@code PropertySource}. Search precedence of local properties is
051 * based on the value of the {@link #setLocalOverride localOverride} property, which is by
052 * default {@code false} meaning that local properties are to be searched last, after all
053 * environment property sources.
054 *
055 * <p>See {@link org.springframework.core.env.ConfigurableEnvironment} and related javadocs
056 * for details on manipulating environment property sources.
057 *
058 * @author Chris Beams
059 * @author Juergen Hoeller
060 * @since 3.1
061 * @see org.springframework.core.env.ConfigurableEnvironment
062 * @see org.springframework.beans.factory.config.PlaceholderConfigurerSupport
063 * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
064 */
065public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
066
067        /**
068         * {@value} is the name given to the {@link PropertySource} for the set of
069         * {@linkplain #mergeProperties() merged properties} supplied to this configurer.
070         */
071        public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";
072
073        /**
074         * {@value} is the name given to the {@link PropertySource} that wraps the
075         * {@linkplain #setEnvironment environment} supplied to this configurer.
076         */
077        public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";
078
079
080        @Nullable
081        private MutablePropertySources propertySources;
082
083        @Nullable
084        private PropertySources appliedPropertySources;
085
086        @Nullable
087        private Environment environment;
088
089
090        /**
091         * Customize the set of {@link PropertySources} to be used by this configurer.
092         * <p>Setting this property indicates that environment property sources and
093         * local properties should be ignored.
094         * @see #postProcessBeanFactory
095         */
096        public void setPropertySources(PropertySources propertySources) {
097                this.propertySources = new MutablePropertySources(propertySources);
098        }
099
100        /**
101         * {@code PropertySources} from the given {@link Environment}
102         * will be searched when replacing ${...} placeholders.
103         * @see #setPropertySources
104         * @see #postProcessBeanFactory
105         */
106        @Override
107        public void setEnvironment(Environment environment) {
108                this.environment = environment;
109        }
110
111
112        /**
113         * Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
114         * against this configurer's set of {@link PropertySources}, which includes:
115         * <ul>
116         * <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
117         * environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
118         * <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
119         * {@linkplain #setLocations have} {@linkplain #setProperties been}
120         * {@linkplain #setPropertiesArray specified}
121         * <li>any property sources set by calling {@link #setPropertySources}
122         * </ul>
123         * <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
124         * ignored</strong>. This method is designed to give the user fine-grained control over property
125         * sources, and once set, the configurer makes no assumptions about adding additional sources.
126         */
127        @Override
128        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
129                if (this.propertySources == null) {
130                        this.propertySources = new MutablePropertySources();
131                        if (this.environment != null) {
132                                this.propertySources.addLast(
133                                        new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
134                                                @Override
135                                                @Nullable
136                                                public String getProperty(String key) {
137                                                        return this.source.getProperty(key);
138                                                }
139                                        }
140                                );
141                        }
142                        try {
143                                PropertySource<?> localPropertySource =
144                                                new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
145                                if (this.localOverride) {
146                                        this.propertySources.addFirst(localPropertySource);
147                                }
148                                else {
149                                        this.propertySources.addLast(localPropertySource);
150                                }
151                        }
152                        catch (IOException ex) {
153                                throw new BeanInitializationException("Could not load properties", ex);
154                        }
155                }
156
157                processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
158                this.appliedPropertySources = this.propertySources;
159        }
160
161        /**
162         * Visit each bean definition in the given bean factory and attempt to replace ${...} property
163         * placeholders with values from the given properties.
164         */
165        protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
166                        final ConfigurablePropertyResolver propertyResolver) throws BeansException {
167
168                propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
169                propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
170                propertyResolver.setValueSeparator(this.valueSeparator);
171
172                StringValueResolver valueResolver = strVal -> {
173                        String resolved = (this.ignoreUnresolvablePlaceholders ?
174                                        propertyResolver.resolvePlaceholders(strVal) :
175                                        propertyResolver.resolveRequiredPlaceholders(strVal));
176                        if (this.trimValues) {
177                                resolved = resolved.trim();
178                        }
179                        return (resolved.equals(this.nullValue) ? null : resolved);
180                };
181
182                doProcessProperties(beanFactoryToProcess, valueResolver);
183        }
184
185        /**
186         * Implemented for compatibility with
187         * {@link org.springframework.beans.factory.config.PlaceholderConfigurerSupport}.
188         * @deprecated in favor of
189         * {@link #processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver)}
190         * @throws UnsupportedOperationException in this implementation
191         */
192        @Override
193        @Deprecated
194        protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) {
195                throw new UnsupportedOperationException(
196                                "Call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver) instead");
197        }
198
199        /**
200         * Return the property sources that were actually applied during
201         * {@link #postProcessBeanFactory(ConfigurableListableBeanFactory) post-processing}.
202         * @return the property sources that were applied
203         * @throws IllegalStateException if the property sources have not yet been applied
204         * @since 4.0
205         */
206        public PropertySources getAppliedPropertySources() throws IllegalStateException {
207                Assert.state(this.appliedPropertySources != null, "PropertySources have not yet been applied");
208                return this.appliedPropertySources;
209        }
210
211}