/*
 * Copyright 2002-2018 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.core.env;

import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.SpringProperties;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

Abstract base class for Environment implementations. Supports the notion of reserved default profile names and enables specifying active and default profiles through the ACTIVE_PROFILES_PROPERTY_NAME and DEFAULT_PROFILES_PROPERTY_NAME properties.

Concrete subclasses differ primarily on which PropertySource objects they add by default. AbstractEnvironment adds none. Subclasses should contribute property sources through the protected customizePropertySources(MutablePropertySources) hook, while clients should customize using ConfigurableEnvironment.getPropertySources() and working against the MutablePropertySources API. See ConfigurableEnvironment javadoc for usage examples.

Author:Chris Beams, Juergen Hoeller
See Also:
Since:3.1
/** * Abstract base class for {@link Environment} implementations. Supports the notion of * reserved default profile names and enables specifying active and default profiles * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties. * * <p>Concrete subclasses differ primarily on which {@link PropertySource} objects they * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute * property sources through the protected {@link #customizePropertySources(MutablePropertySources)} * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()} * and working against the {@link MutablePropertySources} API. * See {@link ConfigurableEnvironment} javadoc for usage examples. * * @author Chris Beams * @author Juergen Hoeller * @since 3.1 * @see ConfigurableEnvironment * @see StandardEnvironment */
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
System property that instructs Spring to ignore system environment variables, i.e. to never attempt to retrieve such a variable via System.getenv().

The default is "false", falling back to system environment variable checks if a Spring environment property (e.g. a placeholder in a configuration String) isn't resolvable otherwise. Consider switching this flag to "true" if you experience log warnings from getenv calls coming from Spring, e.g. on WebSphere with strict SecurityManager settings and AccessControlExceptions warnings.

See Also:
/** * System property that instructs Spring to ignore system environment variables, * i.e. to never attempt to retrieve such a variable via {@link System#getenv()}. * <p>The default is "false", falling back to system environment variable checks if a * Spring environment property (e.g. a placeholder in a configuration String) isn't * resolvable otherwise. Consider switching this flag to "true" if you experience * log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere * with strict SecurityManager settings and AccessControlExceptions warnings. * @see #suppressGetenvAccess() */
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
Name of property to set to specify active profiles: "spring.profiles.active". Value may be comma delimited.

Note that certain shell environments such as Bash disallow the use of the period character in variable names. Assuming that Spring's SystemEnvironmentPropertySource is in use, this property may be specified as an environment variable as SPRING_PROFILES_ACTIVE.

See Also:
/** * Name of property to set to specify active profiles: {@value}. Value may be comma * delimited. * <p>Note that certain shell environments such as Bash disallow the use of the period * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} * is in use, this property may be specified as an environment variable as * {@code SPRING_PROFILES_ACTIVE}. * @see ConfigurableEnvironment#setActiveProfiles */
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
Name of property to set to specify profiles active by default: "spring.profiles.default". Value may be comma delimited.

Note that certain shell environments such as Bash disallow the use of the period character in variable names. Assuming that Spring's SystemEnvironmentPropertySource is in use, this property may be specified as an environment variable as SPRING_PROFILES_DEFAULT.

See Also:
/** * Name of property to set to specify profiles active by default: {@value}. Value may * be comma delimited. * <p>Note that certain shell environments such as Bash disallow the use of the period * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} * is in use, this property may be specified as an environment variable as * {@code SPRING_PROFILES_DEFAULT}. * @see ConfigurableEnvironment#setDefaultProfiles */
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
Name of reserved default profile name: "default". If no default profile names are explicitly and no active profile names are explicitly set, this profile will automatically be activated by default.
See Also:
/** * Name of reserved default profile name: {@value}. If no default profile names are * explicitly and no active profile names are explicitly set, this profile will * automatically be activated by default. * @see #getReservedDefaultProfiles * @see ConfigurableEnvironment#setDefaultProfiles * @see ConfigurableEnvironment#setActiveProfiles * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME */
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default"; protected final Log logger = LogFactory.getLog(getClass()); private final Set<String> activeProfiles = new LinkedHashSet<>(); private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles()); private final MutablePropertySources propertySources = new MutablePropertySources(); private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
Create a new Environment instance, calling back to customizePropertySources(MutablePropertySources) during construction to allow subclasses to contribute or manipulate PropertySource instances as appropriate.
See Also:
/** * Create a new {@code Environment} instance, calling back to * {@link #customizePropertySources(MutablePropertySources)} during construction to * allow subclasses to contribute or manipulate {@link PropertySource} instances as * appropriate. * @see #customizePropertySources(MutablePropertySources) */
public AbstractEnvironment() { customizePropertySources(this.propertySources); }
Customize the set of PropertySource objects to be searched by this Environment during calls to getProperty(String) and related methods.

Subclasses that override this method are encouraged to add property sources using MutablePropertySources.addLast(PropertySource<?>) such that further subclasses may call super.customizePropertySources() with predictable results. For example:

public class Level1Environment extends AbstractEnvironment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources); // no-op from base class
        propertySources.addLast(new PropertySourceA(...));
        propertySources.addLast(new PropertySourceB(...));
    }
}
public class Level2Environment extends Level1Environment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources); // add all from superclass
        propertySources.addLast(new PropertySourceC(...));
        propertySources.addLast(new PropertySourceD(...));
    }
}
In this arrangement, properties will be resolved against sources A, B, C, D in that order. That is to say that property source "A" has precedence over property source "D". If the Level2Environment subclass wished to give property sources C and D higher precedence than A and B, it could simply call super.customizePropertySources after, rather than before adding its own:
public class Level2Environment extends Level1Environment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertySourceC(...));
        propertySources.addLast(new PropertySourceD(...));
        super.customizePropertySources(propertySources); // add all from superclass
    }
}
The search order is now C, D, A, B as desired.

Beyond these recommendations, subclasses may use any of the add&#42;, remove, or replace methods exposed by MutablePropertySources in order to create the exact arrangement of property sources desired.

The base implementation registers no property sources.

Note that clients of any ConfigurableEnvironment may further customize property sources via the getPropertySources() accessor, typically within an ApplicationContextInitializer. For example:

ConfigurableEnvironment env = new StandardEnvironment();
env.getPropertySources().addLast(new PropertySourceX(...));

A warning about instance variable access

Instance variables declared in subclasses and having default initial values should not be accessed from within this method. Due to Java object creation lifecycle constraints, any initial value will not yet be assigned when this callback is invoked by the AbstractEnvironment() constructor, which may lead to a NullPointerException or other problems. If you need to access default values of instance variables, leave this method as a no-op and perform property source manipulation and instance variable access directly within the subclass constructor. Note that assigning values to instance variables is not problematic; it is only attempting to read default values that must be avoided.
See Also:
/** * Customize the set of {@link PropertySource} objects to be searched by this * {@code Environment} during calls to {@link #getProperty(String)} and related * methods. * * <p>Subclasses that override this method are encouraged to add property * sources using {@link MutablePropertySources#addLast(PropertySource)} such that * further subclasses may call {@code super.customizePropertySources()} with * predictable results. For example: * <pre class="code"> * public class Level1Environment extends AbstractEnvironment { * &#064;Override * protected void customizePropertySources(MutablePropertySources propertySources) { * super.customizePropertySources(propertySources); // no-op from base class * propertySources.addLast(new PropertySourceA(...)); * propertySources.addLast(new PropertySourceB(...)); * } * } * * public class Level2Environment extends Level1Environment { * &#064;Override * protected void customizePropertySources(MutablePropertySources propertySources) { * super.customizePropertySources(propertySources); // add all from superclass * propertySources.addLast(new PropertySourceC(...)); * propertySources.addLast(new PropertySourceD(...)); * } * } * </pre> * In this arrangement, properties will be resolved against sources A, B, C, D in that * order. That is to say that property source "A" has precedence over property source * "D". If the {@code Level2Environment} subclass wished to give property sources C * and D higher precedence than A and B, it could simply call * {@code super.customizePropertySources} after, rather than before adding its own: * <pre class="code"> * public class Level2Environment extends Level1Environment { * &#064;Override * protected void customizePropertySources(MutablePropertySources propertySources) { * propertySources.addLast(new PropertySourceC(...)); * propertySources.addLast(new PropertySourceD(...)); * super.customizePropertySources(propertySources); // add all from superclass * } * } * </pre> * The search order is now C, D, A, B as desired. * * <p>Beyond these recommendations, subclasses may use any of the {@code add&#42;}, * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources} * in order to create the exact arrangement of property sources desired. * * <p>The base implementation registers no property sources. * * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize * property sources via the {@link #getPropertySources()} accessor, typically within * an {@link org.springframework.context.ApplicationContextInitializer * ApplicationContextInitializer}. For example: * <pre class="code"> * ConfigurableEnvironment env = new StandardEnvironment(); * env.getPropertySources().addLast(new PropertySourceX(...)); * </pre> * * <h2>A warning about instance variable access</h2> * Instance variables declared in subclasses and having default initial values should * <em>not</em> be accessed from within this method. Due to Java object creation * lifecycle constraints, any initial value will not yet be assigned when this * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may * lead to a {@code NullPointerException} or other problems. If you need to access * default values of instance variables, leave this method as a no-op and perform * property source manipulation and instance variable access directly within the * subclass constructor. Note that <em>assigning</em> values to instance variables is * not problematic; it is only attempting to read default values that must be avoided. * * @see MutablePropertySources * @see PropertySourcesPropertyResolver * @see org.springframework.context.ApplicationContextInitializer */
protected void customizePropertySources(MutablePropertySources propertySources) { }
Return the set of reserved default profile names. This implementation returns "default". Subclasses may override in order to customize the set of reserved names.
See Also:
/** * Return the set of reserved default profile names. This implementation returns * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to * customize the set of reserved names. * @see #RESERVED_DEFAULT_PROFILE_NAME * @see #doGetDefaultProfiles() */
protected Set<String> getReservedDefaultProfiles() { return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME); } //--------------------------------------------------------------------- // Implementation of ConfigurableEnvironment interface //--------------------------------------------------------------------- @Override public String[] getActiveProfiles() { return StringUtils.toStringArray(doGetActiveProfiles()); }
Return the set of active profiles as explicitly set through setActiveProfiles or if the current set of active profiles is empty, check for the presence of the "spring.profiles.active" property and assign its value to the set of active profiles.
See Also:
/** * Return the set of active profiles as explicitly set through * {@link #setActiveProfiles} or if the current set of active profiles * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME} * property and assign its value to the set of active profiles. * @see #getActiveProfiles() * @see #ACTIVE_PROFILES_PROPERTY_NAME */
protected Set<String> doGetActiveProfiles() { synchronized (this.activeProfiles) { if (this.activeProfiles.isEmpty()) { String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME); if (StringUtils.hasText(profiles)) { setActiveProfiles(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(profiles))); } } return this.activeProfiles; } } @Override public void setActiveProfiles(String... profiles) { Assert.notNull(profiles, "Profile array must not be null"); if (logger.isDebugEnabled()) { logger.debug("Activating profiles " + Arrays.asList(profiles)); } synchronized (this.activeProfiles) { this.activeProfiles.clear(); for (String profile : profiles) { validateProfile(profile); this.activeProfiles.add(profile); } } } @Override public void addActiveProfile(String profile) { if (logger.isDebugEnabled()) { logger.debug("Activating profile '" + profile + "'"); } validateProfile(profile); doGetActiveProfiles(); synchronized (this.activeProfiles) { this.activeProfiles.add(profile); } } @Override public String[] getDefaultProfiles() { return StringUtils.toStringArray(doGetDefaultProfiles()); }
Return the set of default profiles explicitly set via setDefaultProfiles(String...) or if the current set of default profiles consists only of reserved default profiles, then check for the presence of the "spring.profiles.default" property and assign its value (if any) to the set of default profiles.
See Also:
/** * Return the set of default profiles explicitly set via * {@link #setDefaultProfiles(String...)} or if the current set of default profiles * consists only of {@linkplain #getReservedDefaultProfiles() reserved default * profiles}, then check for the presence of the * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any) * to the set of default profiles. * @see #AbstractEnvironment() * @see #getDefaultProfiles() * @see #DEFAULT_PROFILES_PROPERTY_NAME * @see #getReservedDefaultProfiles() */
protected Set<String> doGetDefaultProfiles() { synchronized (this.defaultProfiles) { if (this.defaultProfiles.equals(getReservedDefaultProfiles())) { String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME); if (StringUtils.hasText(profiles)) { setDefaultProfiles(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(profiles))); } } return this.defaultProfiles; } }
Specify the set of profiles to be made active by default if no other profiles are explicitly made active through setActiveProfiles.

Calling this method removes overrides any reserved default profiles that may have been added during construction of the environment.

See Also:
/** * Specify the set of profiles to be made active by default if no other profiles * are explicitly made active through {@link #setActiveProfiles}. * <p>Calling this method removes overrides any reserved default profiles * that may have been added during construction of the environment. * @see #AbstractEnvironment() * @see #getReservedDefaultProfiles() */
@Override public void setDefaultProfiles(String... profiles) { Assert.notNull(profiles, "Profile array must not be null"); synchronized (this.defaultProfiles) { this.defaultProfiles.clear(); for (String profile : profiles) { validateProfile(profile); this.defaultProfiles.add(profile); } } } @Override @Deprecated public boolean acceptsProfiles(String... profiles) { Assert.notEmpty(profiles, "Must specify at least one profile"); for (String profile : profiles) { if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') { if (!isProfileActive(profile.substring(1))) { return true; } } else if (isProfileActive(profile)) { return true; } } return false; } @Override public boolean acceptsProfiles(Profiles profiles) { Assert.notNull(profiles, "Profiles must not be null"); return profiles.matches(this::isProfileActive); }
Return whether the given profile is active, or if active profiles are empty whether the profile should be active by default.
Throws:
/** * Return whether the given profile is active, or if active profiles are empty * whether the profile should be active by default. * @throws IllegalArgumentException per {@link #validateProfile(String)} */
protected boolean isProfileActive(String profile) { validateProfile(profile); Set<String> currentActiveProfiles = doGetActiveProfiles(); return (currentActiveProfiles.contains(profile) || (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile))); }
Validate the given profile, called internally prior to adding to the set of active or default profiles.

Subclasses may override to impose further restrictions on profile syntax.

Throws:
  • IllegalArgumentException – if the profile is null, empty, whitespace-only or begins with the profile NOT operator (!).
See Also:
/** * Validate the given profile, called internally prior to adding to the set of * active or default profiles. * <p>Subclasses may override to impose further restrictions on profile syntax. * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or * begins with the profile NOT operator (!). * @see #acceptsProfiles * @see #addActiveProfile * @see #setDefaultProfiles */
protected void validateProfile(String profile) { if (!StringUtils.hasText(profile)) { throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text"); } if (profile.charAt(0) == '!') { throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator"); } } @Override public MutablePropertySources getPropertySources() { return this.propertySources; } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public Map<String, Object> getSystemProperties() { try { return (Map) System.getProperties(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override @Nullable protected String getSystemAttribute(String attributeName) { try { return System.getProperty(attributeName); } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); } return null; } } }; } } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public Map<String, Object> getSystemEnvironment() { if (suppressGetenvAccess()) { return Collections.emptyMap(); } try { return (Map) System.getenv(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override @Nullable protected String getSystemAttribute(String attributeName) { try { return System.getenv(attributeName); } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); } return null; } } }; } }
Determine whether to suppress System.getenv()/System.getenv(String) access for the purposes of getSystemEnvironment().

If this method returns true, an empty dummy Map will be used instead of the regular system environment Map, never even trying to call getenv and therefore avoiding security manager warnings (if any).

The default implementation checks for the "spring.getenv.ignore" system property, returning true if its value equals "true" in any case.

See Also:
/** * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)} * access for the purposes of {@link #getSystemEnvironment()}. * <p>If this method returns {@code true}, an empty dummy Map will be used instead * of the regular system environment Map, never even trying to call {@code getenv} * and therefore avoiding security manager warnings (if any). * <p>The default implementation checks for the "spring.getenv.ignore" system property, * returning {@code true} if its value equals "true" in any case. * @see #IGNORE_GETENV_PROPERTY_NAME * @see SpringProperties#getFlag */
protected boolean suppressGetenvAccess() { return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); } @Override public void merge(ConfigurableEnvironment parent) { for (PropertySource<?> ps : parent.getPropertySources()) { if (!this.propertySources.contains(ps.getName())) { this.propertySources.addLast(ps); } } String[] parentActiveProfiles = parent.getActiveProfiles(); if (!ObjectUtils.isEmpty(parentActiveProfiles)) { synchronized (this.activeProfiles) { for (String profile : parentActiveProfiles) { this.activeProfiles.add(profile); } } } String[] parentDefaultProfiles = parent.getDefaultProfiles(); if (!ObjectUtils.isEmpty(parentDefaultProfiles)) { synchronized (this.defaultProfiles) { this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME); for (String profile : parentDefaultProfiles) { this.defaultProfiles.add(profile); } } } } //--------------------------------------------------------------------- // Implementation of ConfigurablePropertyResolver interface //--------------------------------------------------------------------- @Override public ConfigurableConversionService getConversionService() { return this.propertyResolver.getConversionService(); } @Override public void setConversionService(ConfigurableConversionService conversionService) { this.propertyResolver.setConversionService(conversionService); } @Override public void setPlaceholderPrefix(String placeholderPrefix) { this.propertyResolver.setPlaceholderPrefix(placeholderPrefix); } @Override public void setPlaceholderSuffix(String placeholderSuffix) { this.propertyResolver.setPlaceholderSuffix(placeholderSuffix); } @Override public void setValueSeparator(@Nullable String valueSeparator) { this.propertyResolver.setValueSeparator(valueSeparator); } @Override public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders); } @Override public void setRequiredProperties(String... requiredProperties) { this.propertyResolver.setRequiredProperties(requiredProperties); } @Override public void validateRequiredProperties() throws MissingRequiredPropertiesException { this.propertyResolver.validateRequiredProperties(); } //--------------------------------------------------------------------- // Implementation of PropertyResolver interface //--------------------------------------------------------------------- @Override public boolean containsProperty(String key) { return this.propertyResolver.containsProperty(key); } @Override @Nullable public String getProperty(String key) { return this.propertyResolver.getProperty(key); } @Override public String getProperty(String key, String defaultValue) { return this.propertyResolver.getProperty(key, defaultValue); } @Override @Nullable public <T> T getProperty(String key, Class<T> targetType) { return this.propertyResolver.getProperty(key, targetType); } @Override public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { return this.propertyResolver.getProperty(key, targetType, defaultValue); } @Override public String getRequiredProperty(String key) throws IllegalStateException { return this.propertyResolver.getRequiredProperty(key); } @Override public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException { return this.propertyResolver.getRequiredProperty(key, targetType); } @Override public String resolvePlaceholders(String text) { return this.propertyResolver.resolvePlaceholders(text); } @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { return this.propertyResolver.resolveRequiredPlaceholders(text); } @Override public String toString() { return getClass().getSimpleName() + " {activeProfiles=" + this.activeProfiles + ", defaultProfiles=" + this.defaultProfiles + ", propertySources=" + this.propertySources + "}"; } }