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}