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