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.core.env;
018
019import java.security.AccessControlException;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.LinkedHashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.core.SpringProperties;
030import org.springframework.core.convert.support.ConfigurableConversionService;
031import org.springframework.util.Assert;
032import org.springframework.util.ObjectUtils;
033import org.springframework.util.StringUtils;
034
035/**
036 * Abstract base class for {@link Environment} implementations. Supports the notion of
037 * reserved default profile names and enables specifying active and default profiles
038 * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and
039 * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties.
040 *
041 * <p>Concrete subclasses differ primarily on which {@link PropertySource} objects they
042 * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute
043 * property sources through the protected {@link #customizePropertySources(MutablePropertySources)}
044 * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()}
045 * and working against the {@link MutablePropertySources} API.
046 * See {@link ConfigurableEnvironment} javadoc for usage examples.
047 *
048 * @author Chris Beams
049 * @author Juergen Hoeller
050 * @since 3.1
051 * @see ConfigurableEnvironment
052 * @see StandardEnvironment
053 */
054public abstract class AbstractEnvironment implements ConfigurableEnvironment {
055
056        /**
057         * System property that instructs Spring to ignore system environment variables,
058         * i.e. to never attempt to retrieve such a variable via {@link System#getenv()}.
059         * <p>The default is "false", falling back to system environment variable checks if a
060         * Spring environment property (e.g. a placeholder in a configuration String) isn't
061         * resolvable otherwise. Consider switching this flag to "true" if you experience
062         * log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere
063         * with strict SecurityManager settings and AccessControlExceptions warnings.
064         * @see #suppressGetenvAccess()
065         */
066        public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
067
068        /**
069         * Name of property to set to specify active profiles: {@value}. Value may be comma
070         * delimited.
071         * <p>Note that certain shell environments such as Bash disallow the use of the period
072         * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
073         * is in use, this property may be specified as an environment variable as
074         * {@code SPRING_PROFILES_ACTIVE}.
075         * @see ConfigurableEnvironment#setActiveProfiles
076         */
077        public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
078
079        /**
080         * Name of property to set to specify profiles active by default: {@value}. Value may
081         * be comma delimited.
082         * <p>Note that certain shell environments such as Bash disallow the use of the period
083         * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
084         * is in use, this property may be specified as an environment variable as
085         * {@code SPRING_PROFILES_DEFAULT}.
086         * @see ConfigurableEnvironment#setDefaultProfiles
087         */
088        public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
089
090        /**
091         * Name of reserved default profile name: {@value}. If no default profile names are
092         * explicitly and no active profile names are explicitly set, this profile will
093         * automatically be activated by default.
094         * @see #getReservedDefaultProfiles
095         * @see ConfigurableEnvironment#setDefaultProfiles
096         * @see ConfigurableEnvironment#setActiveProfiles
097         * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
098         * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
099         */
100        protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
101
102
103        protected final Log logger = LogFactory.getLog(getClass());
104
105        private final Set<String> activeProfiles = new LinkedHashSet<String>();
106
107        private final Set<String> defaultProfiles = new LinkedHashSet<String>(getReservedDefaultProfiles());
108
109        private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
110
111        private final ConfigurablePropertyResolver propertyResolver =
112                        new PropertySourcesPropertyResolver(this.propertySources);
113
114
115        /**
116         * Create a new {@code Environment} instance, calling back to
117         * {@link #customizePropertySources(MutablePropertySources)} during construction to
118         * allow subclasses to contribute or manipulate {@link PropertySource} instances as
119         * appropriate.
120         * @see #customizePropertySources(MutablePropertySources)
121         */
122        public AbstractEnvironment() {
123                customizePropertySources(this.propertySources);
124                if (logger.isDebugEnabled()) {
125                        logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
126                }
127        }
128
129
130        /**
131         * Customize the set of {@link PropertySource} objects to be searched by this
132         * {@code Environment} during calls to {@link #getProperty(String)} and related
133         * methods.
134         *
135         * <p>Subclasses that override this method are encouraged to add property
136         * sources using {@link MutablePropertySources#addLast(PropertySource)} such that
137         * further subclasses may call {@code super.customizePropertySources()} with
138         * predictable results. For example:
139         * <pre class="code">
140         * public class Level1Environment extends AbstractEnvironment {
141         *     &#064;Override
142         *     protected void customizePropertySources(MutablePropertySources propertySources) {
143         *         super.customizePropertySources(propertySources); // no-op from base class
144         *         propertySources.addLast(new PropertySourceA(...));
145         *         propertySources.addLast(new PropertySourceB(...));
146         *     }
147         * }
148         *
149         * public class Level2Environment extends Level1Environment {
150         *     &#064;Override
151         *     protected void customizePropertySources(MutablePropertySources propertySources) {
152         *         super.customizePropertySources(propertySources); // add all from superclass
153         *         propertySources.addLast(new PropertySourceC(...));
154         *         propertySources.addLast(new PropertySourceD(...));
155         *     }
156         * }
157         * </pre>
158         * In this arrangement, properties will be resolved against sources A, B, C, D in that
159         * order. That is to say that property source "A" has precedence over property source
160         * "D". If the {@code Level2Environment} subclass wished to give property sources C
161         * and D higher precedence than A and B, it could simply call
162         * {@code super.customizePropertySources} after, rather than before adding its own:
163         * <pre class="code">
164         * public class Level2Environment extends Level1Environment {
165         *     &#064;Override
166         *     protected void customizePropertySources(MutablePropertySources propertySources) {
167         *         propertySources.addLast(new PropertySourceC(...));
168         *         propertySources.addLast(new PropertySourceD(...));
169         *         super.customizePropertySources(propertySources); // add all from superclass
170         *     }
171         * }
172         * </pre>
173         * The search order is now C, D, A, B as desired.
174         *
175         * <p>Beyond these recommendations, subclasses may use any of the {@code add&#42;},
176         * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources}
177         * in order to create the exact arrangement of property sources desired.
178         *
179         * <p>The base implementation registers no property sources.
180         *
181         * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize
182         * property sources via the {@link #getPropertySources()} accessor, typically within
183         * an {@link org.springframework.context.ApplicationContextInitializer
184         * ApplicationContextInitializer}. For example:
185         * <pre class="code">
186         * ConfigurableEnvironment env = new StandardEnvironment();
187         * env.getPropertySources().addLast(new PropertySourceX(...));
188         * </pre>
189         *
190         * <h2>A warning about instance variable access</h2>
191         * Instance variables declared in subclasses and having default initial values should
192         * <em>not</em> be accessed from within this method. Due to Java object creation
193         * lifecycle constraints, any initial value will not yet be assigned when this
194         * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may
195         * lead to a {@code NullPointerException} or other problems. If you need to access
196         * default values of instance variables, leave this method as a no-op and perform
197         * property source manipulation and instance variable access directly within the
198         * subclass constructor. Note that <em>assigning</em> values to instance variables is
199         * not problematic; it is only attempting to read default values that must be avoided.
200         *
201         * @see MutablePropertySources
202         * @see PropertySourcesPropertyResolver
203         * @see org.springframework.context.ApplicationContextInitializer
204         */
205        protected void customizePropertySources(MutablePropertySources propertySources) {
206        }
207
208        /**
209         * Return the set of reserved default profile names. This implementation returns
210         * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to
211         * customize the set of reserved names.
212         * @see #RESERVED_DEFAULT_PROFILE_NAME
213         * @see #doGetDefaultProfiles()
214         */
215        protected Set<String> getReservedDefaultProfiles() {
216                return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
217        }
218
219
220        //---------------------------------------------------------------------
221        // Implementation of ConfigurableEnvironment interface
222        //---------------------------------------------------------------------
223
224        @Override
225        public String[] getActiveProfiles() {
226                return StringUtils.toStringArray(doGetActiveProfiles());
227        }
228
229        /**
230         * Return the set of active profiles as explicitly set through
231         * {@link #setActiveProfiles} or if the current set of active profiles
232         * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
233         * property and assign its value to the set of active profiles.
234         * @see #getActiveProfiles()
235         * @see #ACTIVE_PROFILES_PROPERTY_NAME
236         */
237        protected Set<String> doGetActiveProfiles() {
238                synchronized (this.activeProfiles) {
239                        if (this.activeProfiles.isEmpty()) {
240                                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
241                                if (StringUtils.hasText(profiles)) {
242                                        setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
243                                                        StringUtils.trimAllWhitespace(profiles)));
244                                }
245                        }
246                        return this.activeProfiles;
247                }
248        }
249
250        @Override
251        public void setActiveProfiles(String... profiles) {
252                Assert.notNull(profiles, "Profile array must not be null");
253                if (logger.isDebugEnabled()) {
254                        logger.debug("Activating profiles " + Arrays.asList(profiles));
255                }
256                synchronized (this.activeProfiles) {
257                        this.activeProfiles.clear();
258                        for (String profile : profiles) {
259                                validateProfile(profile);
260                                this.activeProfiles.add(profile);
261                        }
262                }
263        }
264
265        @Override
266        public void addActiveProfile(String profile) {
267                if (logger.isDebugEnabled()) {
268                        logger.debug("Activating profile '" + profile + "'");
269                }
270                validateProfile(profile);
271                doGetActiveProfiles();
272                synchronized (this.activeProfiles) {
273                        this.activeProfiles.add(profile);
274                }
275        }
276
277
278        @Override
279        public String[] getDefaultProfiles() {
280                return StringUtils.toStringArray(doGetDefaultProfiles());
281        }
282
283        /**
284         * Return the set of default profiles explicitly set via
285         * {@link #setDefaultProfiles(String...)} or if the current set of default profiles
286         * consists only of {@linkplain #getReservedDefaultProfiles() reserved default
287         * profiles}, then check for the presence of the
288         * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
289         * to the set of default profiles.
290         * @see #AbstractEnvironment()
291         * @see #getDefaultProfiles()
292         * @see #DEFAULT_PROFILES_PROPERTY_NAME
293         * @see #getReservedDefaultProfiles()
294         */
295        protected Set<String> doGetDefaultProfiles() {
296                synchronized (this.defaultProfiles) {
297                        if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
298                                String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
299                                if (StringUtils.hasText(profiles)) {
300                                        setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
301                                                        StringUtils.trimAllWhitespace(profiles)));
302                                }
303                        }
304                        return this.defaultProfiles;
305                }
306        }
307
308        /**
309         * Specify the set of profiles to be made active by default if no other profiles
310         * are explicitly made active through {@link #setActiveProfiles}.
311         * <p>Calling this method removes overrides any reserved default profiles
312         * that may have been added during construction of the environment.
313         * @see #AbstractEnvironment()
314         * @see #getReservedDefaultProfiles()
315         */
316        @Override
317        public void setDefaultProfiles(String... profiles) {
318                Assert.notNull(profiles, "Profile array must not be null");
319                synchronized (this.defaultProfiles) {
320                        this.defaultProfiles.clear();
321                        for (String profile : profiles) {
322                                validateProfile(profile);
323                                this.defaultProfiles.add(profile);
324                        }
325                }
326        }
327
328        @Override
329        public boolean acceptsProfiles(String... profiles) {
330                Assert.notEmpty(profiles, "Must specify at least one profile");
331                for (String profile : profiles) {
332                        if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
333                                if (!isProfileActive(profile.substring(1))) {
334                                        return true;
335                                }
336                        }
337                        else if (isProfileActive(profile)) {
338                                return true;
339                        }
340                }
341                return false;
342        }
343
344        /**
345         * Return whether the given profile is active, or if active profiles are empty
346         * whether the profile should be active by default.
347         * @throws IllegalArgumentException per {@link #validateProfile(String)}
348         */
349        protected boolean isProfileActive(String profile) {
350                validateProfile(profile);
351                Set<String> currentActiveProfiles = doGetActiveProfiles();
352                return (currentActiveProfiles.contains(profile) ||
353                                (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
354        }
355
356        /**
357         * Validate the given profile, called internally prior to adding to the set of
358         * active or default profiles.
359         * <p>Subclasses may override to impose further restrictions on profile syntax.
360         * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
361         * begins with the profile NOT operator (!).
362         * @see #acceptsProfiles
363         * @see #addActiveProfile
364         * @see #setDefaultProfiles
365         */
366        protected void validateProfile(String profile) {
367                if (!StringUtils.hasText(profile)) {
368                        throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text");
369                }
370                if (profile.charAt(0) == '!') {
371                        throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator");
372                }
373        }
374
375        @Override
376        public MutablePropertySources getPropertySources() {
377                return this.propertySources;
378        }
379
380        @Override
381        @SuppressWarnings({"unchecked", "rawtypes"})
382        public Map<String, Object> getSystemProperties() {
383                try {
384                        return (Map) System.getProperties();
385                }
386                catch (AccessControlException ex) {
387                        return (Map) new ReadOnlySystemAttributesMap() {
388                                @Override
389                                protected String getSystemAttribute(String attributeName) {
390                                        try {
391                                                return System.getProperty(attributeName);
392                                        }
393                                        catch (AccessControlException ex) {
394                                                if (logger.isInfoEnabled()) {
395                                                        logger.info("Caught AccessControlException when accessing system property '" +
396                                                                        attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
397                                                }
398                                                return null;
399                                        }
400                                }
401                        };
402                }
403        }
404
405        @Override
406        @SuppressWarnings({"unchecked", "rawtypes"})
407        public Map<String, Object> getSystemEnvironment() {
408                if (suppressGetenvAccess()) {
409                        return Collections.emptyMap();
410                }
411                try {
412                        return (Map) System.getenv();
413                }
414                catch (AccessControlException ex) {
415                        return (Map) new ReadOnlySystemAttributesMap() {
416                                @Override
417                                protected String getSystemAttribute(String attributeName) {
418                                        try {
419                                                return System.getenv(attributeName);
420                                        }
421                                        catch (AccessControlException ex) {
422                                                if (logger.isInfoEnabled()) {
423                                                        logger.info("Caught AccessControlException when accessing system environment variable '" +
424                                                                        attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
425                                                }
426                                                return null;
427                                        }
428                                }
429                        };
430                }
431        }
432
433        /**
434         * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)}
435         * access for the purposes of {@link #getSystemEnvironment()}.
436         * <p>If this method returns {@code true}, an empty dummy Map will be used instead
437         * of the regular system environment Map, never even trying to call {@code getenv}
438         * and therefore avoiding security manager warnings (if any).
439         * <p>The default implementation checks for the "spring.getenv.ignore" system property,
440         * returning {@code true} if its value equals "true" in any case.
441         * @see #IGNORE_GETENV_PROPERTY_NAME
442         * @see SpringProperties#getFlag
443         */
444        protected boolean suppressGetenvAccess() {
445                return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
446        }
447
448        @Override
449        public void merge(ConfigurableEnvironment parent) {
450                for (PropertySource<?> ps : parent.getPropertySources()) {
451                        if (!this.propertySources.contains(ps.getName())) {
452                                this.propertySources.addLast(ps);
453                        }
454                }
455                String[] parentActiveProfiles = parent.getActiveProfiles();
456                if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
457                        synchronized (this.activeProfiles) {
458                                for (String profile : parentActiveProfiles) {
459                                        this.activeProfiles.add(profile);
460                                }
461                        }
462                }
463                String[] parentDefaultProfiles = parent.getDefaultProfiles();
464                if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
465                        synchronized (this.defaultProfiles) {
466                                this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
467                                for (String profile : parentDefaultProfiles) {
468                                        this.defaultProfiles.add(profile);
469                                }
470                        }
471                }
472        }
473
474
475        //---------------------------------------------------------------------
476        // Implementation of ConfigurablePropertyResolver interface
477        //---------------------------------------------------------------------
478
479        @Override
480        public ConfigurableConversionService getConversionService() {
481                return this.propertyResolver.getConversionService();
482        }
483
484        @Override
485        public void setConversionService(ConfigurableConversionService conversionService) {
486                this.propertyResolver.setConversionService(conversionService);
487        }
488
489        @Override
490        public void setPlaceholderPrefix(String placeholderPrefix) {
491                this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
492        }
493
494        @Override
495        public void setPlaceholderSuffix(String placeholderSuffix) {
496                this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
497        }
498
499        @Override
500        public void setValueSeparator(String valueSeparator) {
501                this.propertyResolver.setValueSeparator(valueSeparator);
502        }
503
504        @Override
505        public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
506                this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders);
507        }
508
509        @Override
510        public void setRequiredProperties(String... requiredProperties) {
511                this.propertyResolver.setRequiredProperties(requiredProperties);
512        }
513
514        @Override
515        public void validateRequiredProperties() throws MissingRequiredPropertiesException {
516                this.propertyResolver.validateRequiredProperties();
517        }
518
519
520        //---------------------------------------------------------------------
521        // Implementation of PropertyResolver interface
522        //---------------------------------------------------------------------
523
524        @Override
525        public boolean containsProperty(String key) {
526                return this.propertyResolver.containsProperty(key);
527        }
528
529        @Override
530        public String getProperty(String key) {
531                return this.propertyResolver.getProperty(key);
532        }
533
534        @Override
535        public String getProperty(String key, String defaultValue) {
536                return this.propertyResolver.getProperty(key, defaultValue);
537        }
538
539        @Override
540        public <T> T getProperty(String key, Class<T> targetType) {
541                return this.propertyResolver.getProperty(key, targetType);
542        }
543
544        @Override
545        public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
546                return this.propertyResolver.getProperty(key, targetType, defaultValue);
547        }
548
549        @Override
550        @Deprecated
551        public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
552                return this.propertyResolver.getPropertyAsClass(key, targetType);
553        }
554
555        @Override
556        public String getRequiredProperty(String key) throws IllegalStateException {
557                return this.propertyResolver.getRequiredProperty(key);
558        }
559
560        @Override
561        public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
562                return this.propertyResolver.getRequiredProperty(key, targetType);
563        }
564
565        @Override
566        public String resolvePlaceholders(String text) {
567                return this.propertyResolver.resolvePlaceholders(text);
568        }
569
570        @Override
571        public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
572                return this.propertyResolver.resolveRequiredPlaceholders(text);
573        }
574
575
576        @Override
577        public String toString() {
578                return getClass().getSimpleName() + " {activeProfiles=" + this.activeProfiles +
579                                ", defaultProfiles=" + this.defaultProfiles + ", propertySources=" + this.propertySources + "}";
580        }
581
582}