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}