1 /*
2  * Copyright 2002-2012 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.core.env;
18 
19 import java.security.AccessControlException;
20 import java.util.Collections;
21 import java.util.LinkedHashSet;
22 import java.util.Map;
23 import java.util.Set;
24 
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 
28 import org.springframework.core.convert.support.ConfigurableConversionService;
29 import org.springframework.util.Assert;
30 import org.springframework.util.StringUtils;
31 
32 import static java.lang.String.*;
33 import static org.springframework.util.StringUtils.*;
34 
35 /**
36  * Abstract base class for {@link Environment} implementations. Supports the notion of
37  * reserved default profile names and enables specifying active and default profiles
38  * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and
39  * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties.
40  *
41  * <p>Concrete subclasses differ primarily on which {@link PropertySource} objects they
42  * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute
43  * property sources through the protected {@link #customizePropertySources(MutablePropertySources)}
44  * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()}
45  * and working against the {@link MutablePropertySources} API. See
46  * {@link ConfigurableEnvironment} Javadoc for usage examples.
47  *
48  * @author Chris Beams
49  * @since 3.1
50  * @see ConfigurableEnvironment
51  * @see StandardEnvironment
52  */
53 public abstract class AbstractEnvironment implements ConfigurableEnvironment {
54 
55 	/**
56 	 * Name of property to set to specify active profiles: {@value}. Value may be comma
57 	 * delimited.
58 	 * <p>Note that certain shell environments such as Bash disallow the use of the period
59 	 * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
60 	 * is in use, this property may be specified as an environment variable as
61 	 * {@code SPRING_PROFILES_ACTIVE}.
62 	 * @see ConfigurableEnvironment#setActiveProfiles
63 	 */
64 	public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
65 
66 	/**
67 	 * Name of property to set to specify profiles active by default: {@value}. Value may
68 	 * be comma delimited.
69 	 * <p>Note that certain shell environments such as Bash disallow the use of the period
70 	 * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
71 	 * is in use, this property may be specified as an environment variable as
72 	 * {@code SPRING_PROFILES_DEFAULT}.
73 	 * @see ConfigurableEnvironment#setDefaultProfiles
74 	 */
75 	public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
76 
77 	/**
78 	 * Name of reserved default profile name: {@value}. If no default profile names are
79 	 * explicitly and no active profile names are explicitly set, this profile will
80 	 * automatically be activated by default.
81 	 * @see #getReservedDefaultProfiles
82 	 * @see ConfigurableEnvironment#setDefaultProfiles
83 	 * @see ConfigurableEnvironment#setActiveProfiles
84 	 * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
85 	 * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
86 	 */
87 	protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
88 
89 
90 	protected final Log logger = LogFactory.getLog(getClass());
91 
92 	private Set<String> activeProfiles = new LinkedHashSet<String>();
93 
94 	private Set<String> defaultProfiles = new LinkedHashSet<String>(getReservedDefaultProfiles());
95 
96 	private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
97 
98 	private final ConfigurablePropertyResolver propertyResolver =
99 			new PropertySourcesPropertyResolver(this.propertySources);
100 
101 
102 	/**
103 	 * Create a new {@code Environment} instance, calling back to
104 	 * {@link #customizePropertySources(MutablePropertySources)} during construction to
105 	 * allow subclasses to contribute or manipulate {@link PropertySource} instances as
106 	 * appropriate.
107 	 * @see #customizePropertySources(MutablePropertySources)
108 	 */
AbstractEnvironment()109 	public AbstractEnvironment() {
110 		String name = getClass().getSimpleName();
111 		if (this.logger.isDebugEnabled()) {
112 			this.logger.debug(format("Initializing new %s", name));
113 		}
114 		customizePropertySources(this.propertySources);
115 		if (this.logger.isDebugEnabled()) {
116 			this.logger.debug(format(
117 					"Initialized %s with PropertySources %s", name, this.propertySources));
118 		}
119 	}
120 
121 
122 	/**
123 	 * Customize the set of {@link PropertySource} objects to be searched by this
124 	 * {@code Environment} during calls to {@link #getProperty(String)} and related
125 	 * methods.
126 	 *
127 	 * <p>Subclasses that override this method are encouraged to add property
128 	 * sources using {@link MutablePropertySources#addLast(PropertySource)} such that
129 	 * further subclasses may call {@code super.customizePropertySources()} with
130 	 * predictable results. For example:
131 	 * <pre class="code">
132 	 * public class Level1Environment extends AbstractEnvironment {
133 	 *     &#064;Override
134 	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
135 	 *         super.customizePropertySources(propertySources); // no-op from base class
136 	 *         propertySources.addLast(new PropertySourceA(...));
137 	 *         propertySources.addLast(new PropertySourceB(...));
138 	 *     }
139 	 * }
140 	 *
141 	 * public class Level2Environment extends Level1Environment {
142 	 *     &#064;Override
143 	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
144 	 *         super.customizePropertySources(propertySources); // add all from superclass
145 	 *         propertySources.addLast(new PropertySourceC(...));
146 	 *         propertySources.addLast(new PropertySourceD(...));
147 	 *     }
148 	 * }
149 	 * </pre>
150 	 * In this arrangement, properties will be resolved against sources A, B, C, D in that
151 	 * order. That is to say that property source "A" has precedence over property source
152 	 * "D". If the {@code Level2Environment} subclass wished to give property sources C
153 	 * and D higher precedence than A and B, it could simply call
154 	 * {@code super.customizePropertySources} after, rather than before adding its own:
155 	 * <pre class="code">
156 	 * public class Level2Environment extends Level1Environment {
157 	 *     &#064;Override
158 	 *     protected void customizePropertySources(MutablePropertySources propertySources) {
159 	 *         propertySources.addLast(new PropertySourceC(...));
160 	 *         propertySources.addLast(new PropertySourceD(...));
161 	 *         super.customizePropertySources(propertySources); // add all from superclass
162 	 *     }
163 	 * }
164 	 * </pre>
165 	 * The search order is now C, D, A, B as desired.
166 	 *
167 	 * <p>Beyond these recommendations, subclasses may use any of the <code>add&#42;</code>,
168 	 * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources}
169 	 * in order to create the exact arrangement of property sources desired.
170 	 *
171 	 * <p>The base implementation in {@link AbstractEnvironment#customizePropertySources}
172 	 * registers no property sources.
173 	 *
174 	 * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize
175 	 * property sources via the {@link #getPropertySources()} accessor, typically within
176 	 * an {@link org.springframework.context.ApplicationContextInitializer
177 	 * ApplicationContextInitializer}. For example:
178 	 * <pre class="code">
179 	 * ConfigurableEnvironment env = new StandardEnvironment();
180 	 * env.getPropertySources().addLast(new PropertySourceX(...));
181 	 * </pre>
182 	 *
183 	 * <h2>A warning about instance variable access</h2>
184 	 * Instance variables declared in subclasses and having default initial values should
185 	 * <em>not</em> be accessed from within this method. Due to Java object creation
186 	 * lifecycle constraints, any initial value will not yet be assigned when this
187 	 * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may
188 	 * lead to a {@code NullPointerException} or other problems. If you need to access
189 	 * default values of instance variables, leave this method as a no-op and perform
190 	 * property source manipulation and instance variable access directly within the
191 	 * subclass constructor. Note that <em>assigning</em> values to instance variables is
192 	 * not problematic; it is only attempting to read default values that must be avoided.
193 	 *
194 	 * @see MutablePropertySources
195 	 * @see PropertySourcesPropertyResolver
196 	 * @see org.springframework.context.ApplicationContextInitializer
197 	 */
customizePropertySources(MutablePropertySources propertySources)198 	protected void customizePropertySources(MutablePropertySources propertySources) {
199 	}
200 
201 	/**
202 	 * Return the set of reserved default profile names. This implementation returns
203 	 * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to
204 	 * customize the set of reserved names.
205 	 * @see #RESERVED_DEFAULT_PROFILE_NAME
206 	 * @see #doGetDefaultProfiles()
207 	 */
getReservedDefaultProfiles()208 	protected Set<String> getReservedDefaultProfiles() {
209 		return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
210 	}
211 
212 
213 	//---------------------------------------------------------------------
214 	// Implementation of ConfigurableEnvironment interface
215 	//---------------------------------------------------------------------
216 
getActiveProfiles()217 	public String[] getActiveProfiles() {
218 		return StringUtils.toStringArray(doGetActiveProfiles());
219 	}
220 
221 	/**
222 	 * Return the set of active profiles as explicitly set through
223 	 * {@link #setActiveProfiles} or if the current set of active profiles
224 	 * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
225 	 * property and assign its value to the set of active profiles.
226 	 * @see #getActiveProfiles()
227 	 * @see #ACTIVE_PROFILES_PROPERTY_NAME
228 	 */
doGetActiveProfiles()229 	protected Set<String> doGetActiveProfiles() {
230 		if (this.activeProfiles.isEmpty()) {
231 			String profiles = this.getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
232 			if (StringUtils.hasText(profiles)) {
233 				setActiveProfiles(commaDelimitedListToStringArray(trimAllWhitespace(profiles)));
234 			}
235 		}
236 		return this.activeProfiles;
237 	}
238 
setActiveProfiles(String... profiles)239 	public void setActiveProfiles(String... profiles) {
240 		Assert.notNull(profiles, "Profile array must not be null");
241 		this.activeProfiles.clear();
242 		for (String profile : profiles) {
243 			addActiveProfile(profile);
244 		}
245 	}
246 
addActiveProfile(String profile)247 	public void addActiveProfile(String profile) {
248 		if (this.logger.isDebugEnabled()) {
249 			this.logger.debug(format("Activating profile '%s'", profile));
250 		}
251 		validateProfile(profile);
252 		this.activeProfiles.add(profile);
253 	}
254 
getDefaultProfiles()255 	public String[] getDefaultProfiles() {
256 		return StringUtils.toStringArray(doGetDefaultProfiles());
257 	}
258 
259 	/**
260 	 * Return the set of default profiles explicitly set via
261 	 * {@link #setDefaultProfiles(String...)} or if the current set of default profiles
262 	 * consists only of {@linkplain #getReservedDefaultProfiles() reserved default
263 	 * profiles}, then check for the presence of the
264 	 * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
265 	 * to the set of default profiles.
266 	 * @see #AbstractEnvironment()
267 	 * @see #getDefaultProfiles()
268 	 * @see #DEFAULT_PROFILES_PROPERTY_NAME
269 	 * @see #getReservedDefaultProfiles()
270 	 */
doGetDefaultProfiles()271 	protected Set<String> doGetDefaultProfiles() {
272 		if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
273 			String profiles = this.getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
274 			if (StringUtils.hasText(profiles)) {
275 				setDefaultProfiles(commaDelimitedListToStringArray(trimAllWhitespace(profiles)));
276 			}
277 		}
278 		return this.defaultProfiles;
279 	}
280 
281 	/**
282 	 * {@inheritDoc}
283 	 * <p>Calling this method removes overrides any reserved default profiles
284 	 * that may have been added during construction of the environment.
285 	 * @see #AbstractEnvironment()
286 	 * @see #getReservedDefaultProfiles()
287 	 */
setDefaultProfiles(String... profiles)288 	public void setDefaultProfiles(String... profiles) {
289 		Assert.notNull(profiles, "Profile array must not be null");
290 		this.defaultProfiles.clear();
291 		for (String profile : profiles) {
292 			validateProfile(profile);
293 			this.defaultProfiles.add(profile);
294 		}
295 	}
296 
acceptsProfiles(String... profiles)297 	public boolean acceptsProfiles(String... profiles) {
298 		Assert.notEmpty(profiles, "Must specify at least one profile");
299 		for (String profile : profiles) {
300 			if (profile != null && profile.length() > 0 && profile.charAt(0) == '!') {
301 				return !isProfileActive(profile.substring(1));
302 			}
303 			if (isProfileActive(profile)) {
304 				return true;
305 			}
306 		}
307 		return false;
308 	}
309 
310 	/**
311 	 * Return whether the given profile is active, or if active profiles are empty
312 	 * whether the profile should be active by default.
313 	 * @throws IllegalArgumentException per {@link #validateProfile(String)}
314 	 */
isProfileActive(String profile)315 	protected boolean isProfileActive(String profile) {
316 		validateProfile(profile);
317 		return doGetActiveProfiles().contains(profile) ||
318 				(doGetActiveProfiles().isEmpty() && doGetDefaultProfiles().contains(profile));
319 	}
320 
321 	/**
322 	 * Validate the given profile, called internally prior to adding to the set of
323 	 * active or default profiles.
324 	 * <p>Subclasses may override to impose further restrictions on profile syntax.
325 	 * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
326 	 * begins with the profile NOT operator (!).
327 	 * @see #acceptsProfiles
328 	 * @see #addActiveProfile
329 	 * @see #setDefaultProfiles
330 	 */
validateProfile(String profile)331 	protected void validateProfile(String profile) {
332 		Assert.hasText(profile, "Invalid profile [" + profile + "]: must contain text");
333 		Assert.isTrue(profile.charAt(0) != '!',
334 				"Invalid profile [" + profile + "]: must not begin with the ! operator");
335 	}
336 
getPropertySources()337 	public MutablePropertySources getPropertySources() {
338 		return this.propertySources;
339 	}
340 
341 	@SuppressWarnings("unchecked")
getSystemEnvironment()342 	public Map<String, Object> getSystemEnvironment() {
343 		Map<String, ?> systemEnvironment;
344 		try {
345 			systemEnvironment = System.getenv();
346 		}
347 		catch (AccessControlException ex) {
348 			systemEnvironment = new ReadOnlySystemAttributesMap() {
349 				@Override
350 				protected String getSystemAttribute(String variableName) {
351 					try {
352 						return System.getenv(variableName);
353 					}
354 					catch (AccessControlException ex) {
355 						if (logger.isInfoEnabled()) {
356 							logger.info(format("Caught AccessControlException when " +
357 									"accessing system environment variable [%s]; its " +
358 									"value will be returned [null]. Reason: %s",
359 									variableName, ex.getMessage()));
360 						}
361 						return null;
362 					}
363 				}
364 			};
365 		}
366 		return (Map<String, Object>) systemEnvironment;
367 	}
368 
369 	@SuppressWarnings({"unchecked", "rawtypes"})
getSystemProperties()370 	public Map<String, Object> getSystemProperties() {
371 		Map systemProperties;
372 		try {
373 			systemProperties = System.getProperties();
374 		}
375 		catch (AccessControlException ex) {
376 			systemProperties = new ReadOnlySystemAttributesMap() {
377 				@Override
378 				protected String getSystemAttribute(String propertyName) {
379 					try {
380 						return System.getProperty(propertyName);
381 					}
382 					catch (AccessControlException ex) {
383 						if (logger.isInfoEnabled()) {
384 							logger.info(format("Caught AccessControlException when " +
385 									"accessing system property [%s]; its value will be " +
386 									"returned [null]. Reason: %s",
387 									propertyName, ex.getMessage()));
388 						}
389 						return null;
390 					}
391 				}
392 			};
393 		}
394 		return systemProperties;
395 	}
396 
merge(ConfigurableEnvironment parent)397 	public void merge(ConfigurableEnvironment parent) {
398 		for (PropertySource<?> ps : parent.getPropertySources()) {
399 			if (!this.propertySources.contains(ps.getName())) {
400 				this.propertySources.addLast(ps);
401 			}
402 		}
403 		for (String profile : parent.getActiveProfiles()) {
404 			this.activeProfiles.add(profile);
405 		}
406 		if (parent.getDefaultProfiles().length > 0) {
407 			this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
408 			for (String profile : parent.getDefaultProfiles()) {
409 				this.defaultProfiles.add(profile);
410 			}
411 		}
412 	}
413 
414 
415 	//---------------------------------------------------------------------
416 	// Implementation of ConfigurablePropertyResolver interface
417 	//---------------------------------------------------------------------
418 
containsProperty(String key)419 	public boolean containsProperty(String key) {
420 		return this.propertyResolver.containsProperty(key);
421 	}
422 
getProperty(String key)423 	public String getProperty(String key) {
424 		return this.propertyResolver.getProperty(key);
425 	}
426 
getProperty(String key, String defaultValue)427 	public String getProperty(String key, String defaultValue) {
428 		return this.propertyResolver.getProperty(key, defaultValue);
429 	}
430 
getProperty(String key, Class<T> targetType)431 	public <T> T getProperty(String key, Class<T> targetType) {
432 		return this.propertyResolver.getProperty(key, targetType);
433 	}
434 
getProperty(String key, Class<T> targetType, T defaultValue)435 	public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
436 		return this.propertyResolver.getProperty(key, targetType, defaultValue);
437 	};
438 
getPropertyAsClass(String key, Class<T> targetType)439 	public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
440 		return this.propertyResolver.getPropertyAsClass(key, targetType);
441 	}
442 
getRequiredProperty(String key)443 	public String getRequiredProperty(String key) throws IllegalStateException {
444 		return this.propertyResolver.getRequiredProperty(key);
445 	}
446 
getRequiredProperty(String key, Class<T> targetType)447 	public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
448 		return this.propertyResolver.getRequiredProperty(key, targetType);
449 	}
450 
setRequiredProperties(String... requiredProperties)451 	public void setRequiredProperties(String... requiredProperties) {
452 		this.propertyResolver.setRequiredProperties(requiredProperties);
453 	}
454 
validateRequiredProperties()455 	public void validateRequiredProperties() throws MissingRequiredPropertiesException {
456 		this.propertyResolver.validateRequiredProperties();
457 	}
458 
resolvePlaceholders(String text)459 	public String resolvePlaceholders(String text) {
460 		return this.propertyResolver.resolvePlaceholders(text);
461 	}
462 
resolveRequiredPlaceholders(String text)463 	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
464 		return this.propertyResolver.resolveRequiredPlaceholders(text);
465 	}
466 
setConversionService(ConfigurableConversionService conversionService)467 	public void setConversionService(ConfigurableConversionService conversionService) {
468 		this.propertyResolver.setConversionService(conversionService);
469 	}
470 
getConversionService()471 	public ConfigurableConversionService getConversionService() {
472 		return this.propertyResolver.getConversionService();
473 	}
474 
setPlaceholderPrefix(String placeholderPrefix)475 	public void setPlaceholderPrefix(String placeholderPrefix) {
476 		this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
477 	}
478 
setPlaceholderSuffix(String placeholderSuffix)479 	public void setPlaceholderSuffix(String placeholderSuffix) {
480 		this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
481 	}
482 
setValueSeparator(String valueSeparator)483 	public void setValueSeparator(String valueSeparator) {
484 		this.propertyResolver.setValueSeparator(valueSeparator);
485 	}
486 
487 
488 	@Override
toString()489 	public String toString() {
490 		return format("%s {activeProfiles=%s, defaultProfiles=%s, propertySources=%s}",
491 				getClass().getSimpleName(), this.activeProfiles, this.defaultProfiles,
492 				this.propertySources);
493 	}
494 
495 }
496