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