001/*
002 * Copyright 2002-2015 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.util.Map;
020
021import org.springframework.lang.Nullable;
022import org.springframework.util.Assert;
023
024/**
025 * Specialization of {@link MapPropertySource} designed for use with
026 * {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}.
027 * Compensates for constraints in Bash and other shells that do not allow for variables
028 * containing the period character and/or hyphen character; also allows for uppercase
029 * variations on property names for more idiomatic shell use.
030 *
031 * <p>For example, a call to {@code getProperty("foo.bar")} will attempt to find a value
032 * for the original property or any 'equivalent' property, returning the first found:
033 * <ul>
034 * <li>{@code foo.bar} - the original name</li>
035 * <li>{@code foo_bar} - with underscores for periods (if any)</li>
036 * <li>{@code FOO.BAR} - original, with upper case</li>
037 * <li>{@code FOO_BAR} - with underscores and upper case</li>
038 * </ul>
039 * Any hyphen variant of the above would work as well, or even mix dot/hyphen variants.
040 *
041 * <p>The same applies for calls to {@link #containsProperty(String)}, which returns
042 * {@code true} if any of the above properties are present, otherwise {@code false}.
043 *
044 * <p>This feature is particularly useful when specifying active or default profiles as
045 * environment variables. The following is not allowable under Bash:
046 *
047 * <pre class="code">spring.profiles.active=p1 java -classpath ... MyApp</pre>
048 *
049 * However, the following syntax is permitted and is also more conventional:
050 *
051 * <pre class="code">SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp</pre>
052 *
053 * <p>Enable debug- or trace-level logging for this class (or package) for messages
054 * explaining when these 'property name resolutions' occur.
055 *
056 * <p>This property source is included by default in {@link StandardEnvironment}
057 * and all its subclasses.
058 *
059 * @author Chris Beams
060 * @author Juergen Hoeller
061 * @since 3.1
062 * @see StandardEnvironment
063 * @see AbstractEnvironment#getSystemEnvironment()
064 * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
065 */
066public class SystemEnvironmentPropertySource extends MapPropertySource {
067
068        /**
069         * Create a new {@code SystemEnvironmentPropertySource} with the given name and
070         * delegating to the given {@code MapPropertySource}.
071         */
072        public SystemEnvironmentPropertySource(String name, Map<String, Object> source) {
073                super(name, source);
074        }
075
076
077        /**
078         * Return {@code true} if a property with the given name or any underscore/uppercase variant
079         * thereof exists in this property source.
080         */
081        @Override
082        public boolean containsProperty(String name) {
083                return (getProperty(name) != null);
084        }
085
086        /**
087         * This implementation returns {@code true} if a property with the given name or
088         * any underscore/uppercase variant thereof exists in this property source.
089         */
090        @Override
091        @Nullable
092        public Object getProperty(String name) {
093                String actualName = resolvePropertyName(name);
094                if (logger.isDebugEnabled() && !name.equals(actualName)) {
095                        logger.debug("PropertySource '" + getName() + "' does not contain property '" + name +
096                                        "', but found equivalent '" + actualName + "'");
097                }
098                return super.getProperty(actualName);
099        }
100
101        /**
102         * Check to see if this property source contains a property with the given name, or
103         * any underscore / uppercase variation thereof. Return the resolved name if one is
104         * found or otherwise the original name. Never returns {@code null}.
105         */
106        protected final String resolvePropertyName(String name) {
107                Assert.notNull(name, "Property name must not be null");
108                String resolvedName = checkPropertyName(name);
109                if (resolvedName != null) {
110                        return resolvedName;
111                }
112                String uppercasedName = name.toUpperCase();
113                if (!name.equals(uppercasedName)) {
114                        resolvedName = checkPropertyName(uppercasedName);
115                        if (resolvedName != null) {
116                                return resolvedName;
117                        }
118                }
119                return name;
120        }
121
122        @Nullable
123        private String checkPropertyName(String name) {
124                // Check name as-is
125                if (containsKey(name)) {
126                        return name;
127                }
128                // Check name with just dots replaced
129                String noDotName = name.replace('.', '_');
130                if (!name.equals(noDotName) && containsKey(noDotName)) {
131                        return noDotName;
132                }
133                // Check name with just hyphens replaced
134                String noHyphenName = name.replace('-', '_');
135                if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
136                        return noHyphenName;
137                }
138                // Check name with dots and hyphens replaced
139                String noDotNoHyphenName = noDotName.replace('-', '_');
140                if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
141                        return noDotNoHyphenName;
142                }
143                // Give up
144                return null;
145        }
146
147        private boolean containsKey(String name) {
148                return (isSecurityManagerPresent() ? this.source.keySet().contains(name) : this.source.containsKey(name));
149        }
150
151        protected boolean isSecurityManagerPresent() {
152                return (System.getSecurityManager() != null);
153        }
154
155}