001/* 002 * Copyright 2002-2018 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.security.AccessControlException; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.LinkedHashSet; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.core.SpringProperties; 030import org.springframework.core.convert.support.ConfigurableConversionService; 031import org.springframework.util.Assert; 032import org.springframework.util.ObjectUtils; 033import org.springframework.util.StringUtils; 034 035/** 036 * Abstract base class for {@link Environment} implementations. Supports the notion of 037 * reserved default profile names and enables specifying active and default profiles 038 * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and 039 * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties. 040 * 041 * <p>Concrete subclasses differ primarily on which {@link PropertySource} objects they 042 * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute 043 * property sources through the protected {@link #customizePropertySources(MutablePropertySources)} 044 * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()} 045 * and working against the {@link MutablePropertySources} API. 046 * See {@link ConfigurableEnvironment} javadoc for usage examples. 047 * 048 * @author Chris Beams 049 * @author Juergen Hoeller 050 * @since 3.1 051 * @see ConfigurableEnvironment 052 * @see StandardEnvironment 053 */ 054public abstract class AbstractEnvironment implements ConfigurableEnvironment { 055 056 /** 057 * System property that instructs Spring to ignore system environment variables, 058 * i.e. to never attempt to retrieve such a variable via {@link System#getenv()}. 059 * <p>The default is "false", falling back to system environment variable checks if a 060 * Spring environment property (e.g. a placeholder in a configuration String) isn't 061 * resolvable otherwise. Consider switching this flag to "true" if you experience 062 * log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere 063 * with strict SecurityManager settings and AccessControlExceptions warnings. 064 * @see #suppressGetenvAccess() 065 */ 066 public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore"; 067 068 /** 069 * Name of property to set to specify active profiles: {@value}. Value may be comma 070 * delimited. 071 * <p>Note that certain shell environments such as Bash disallow the use of the period 072 * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} 073 * is in use, this property may be specified as an environment variable as 074 * {@code SPRING_PROFILES_ACTIVE}. 075 * @see ConfigurableEnvironment#setActiveProfiles 076 */ 077 public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; 078 079 /** 080 * Name of property to set to specify profiles active by default: {@value}. Value may 081 * be comma delimited. 082 * <p>Note that certain shell environments such as Bash disallow the use of the period 083 * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource} 084 * is in use, this property may be specified as an environment variable as 085 * {@code SPRING_PROFILES_DEFAULT}. 086 * @see ConfigurableEnvironment#setDefaultProfiles 087 */ 088 public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default"; 089 090 /** 091 * Name of reserved default profile name: {@value}. If no default profile names are 092 * explicitly and no active profile names are explicitly set, this profile will 093 * automatically be activated by default. 094 * @see #getReservedDefaultProfiles 095 * @see ConfigurableEnvironment#setDefaultProfiles 096 * @see ConfigurableEnvironment#setActiveProfiles 097 * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME 098 * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME 099 */ 100 protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default"; 101 102 103 protected final Log logger = LogFactory.getLog(getClass()); 104 105 private final Set<String> activeProfiles = new LinkedHashSet<String>(); 106 107 private final Set<String> defaultProfiles = new LinkedHashSet<String>(getReservedDefaultProfiles()); 108 109 private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); 110 111 private final ConfigurablePropertyResolver propertyResolver = 112 new PropertySourcesPropertyResolver(this.propertySources); 113 114 115 /** 116 * Create a new {@code Environment} instance, calling back to 117 * {@link #customizePropertySources(MutablePropertySources)} during construction to 118 * allow subclasses to contribute or manipulate {@link PropertySource} instances as 119 * appropriate. 120 * @see #customizePropertySources(MutablePropertySources) 121 */ 122 public AbstractEnvironment() { 123 customizePropertySources(this.propertySources); 124 if (logger.isDebugEnabled()) { 125 logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources); 126 } 127 } 128 129 130 /** 131 * Customize the set of {@link PropertySource} objects to be searched by this 132 * {@code Environment} during calls to {@link #getProperty(String)} and related 133 * methods. 134 * 135 * <p>Subclasses that override this method are encouraged to add property 136 * sources using {@link MutablePropertySources#addLast(PropertySource)} such that 137 * further subclasses may call {@code super.customizePropertySources()} with 138 * predictable results. For example: 139 * <pre class="code"> 140 * public class Level1Environment extends AbstractEnvironment { 141 * @Override 142 * protected void customizePropertySources(MutablePropertySources propertySources) { 143 * super.customizePropertySources(propertySources); // no-op from base class 144 * propertySources.addLast(new PropertySourceA(...)); 145 * propertySources.addLast(new PropertySourceB(...)); 146 * } 147 * } 148 * 149 * public class Level2Environment extends Level1Environment { 150 * @Override 151 * protected void customizePropertySources(MutablePropertySources propertySources) { 152 * super.customizePropertySources(propertySources); // add all from superclass 153 * propertySources.addLast(new PropertySourceC(...)); 154 * propertySources.addLast(new PropertySourceD(...)); 155 * } 156 * } 157 * </pre> 158 * In this arrangement, properties will be resolved against sources A, B, C, D in that 159 * order. That is to say that property source "A" has precedence over property source 160 * "D". If the {@code Level2Environment} subclass wished to give property sources C 161 * and D higher precedence than A and B, it could simply call 162 * {@code super.customizePropertySources} after, rather than before adding its own: 163 * <pre class="code"> 164 * public class Level2Environment extends Level1Environment { 165 * @Override 166 * protected void customizePropertySources(MutablePropertySources propertySources) { 167 * propertySources.addLast(new PropertySourceC(...)); 168 * propertySources.addLast(new PropertySourceD(...)); 169 * super.customizePropertySources(propertySources); // add all from superclass 170 * } 171 * } 172 * </pre> 173 * The search order is now C, D, A, B as desired. 174 * 175 * <p>Beyond these recommendations, subclasses may use any of the {@code add*}, 176 * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources} 177 * in order to create the exact arrangement of property sources desired. 178 * 179 * <p>The base implementation registers no property sources. 180 * 181 * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize 182 * property sources via the {@link #getPropertySources()} accessor, typically within 183 * an {@link org.springframework.context.ApplicationContextInitializer 184 * ApplicationContextInitializer}. For example: 185 * <pre class="code"> 186 * ConfigurableEnvironment env = new StandardEnvironment(); 187 * env.getPropertySources().addLast(new PropertySourceX(...)); 188 * </pre> 189 * 190 * <h2>A warning about instance variable access</h2> 191 * Instance variables declared in subclasses and having default initial values should 192 * <em>not</em> be accessed from within this method. Due to Java object creation 193 * lifecycle constraints, any initial value will not yet be assigned when this 194 * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may 195 * lead to a {@code NullPointerException} or other problems. If you need to access 196 * default values of instance variables, leave this method as a no-op and perform 197 * property source manipulation and instance variable access directly within the 198 * subclass constructor. Note that <em>assigning</em> values to instance variables is 199 * not problematic; it is only attempting to read default values that must be avoided. 200 * 201 * @see MutablePropertySources 202 * @see PropertySourcesPropertyResolver 203 * @see org.springframework.context.ApplicationContextInitializer 204 */ 205 protected void customizePropertySources(MutablePropertySources propertySources) { 206 } 207 208 /** 209 * Return the set of reserved default profile names. This implementation returns 210 * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to 211 * customize the set of reserved names. 212 * @see #RESERVED_DEFAULT_PROFILE_NAME 213 * @see #doGetDefaultProfiles() 214 */ 215 protected Set<String> getReservedDefaultProfiles() { 216 return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME); 217 } 218 219 220 //--------------------------------------------------------------------- 221 // Implementation of ConfigurableEnvironment interface 222 //--------------------------------------------------------------------- 223 224 @Override 225 public String[] getActiveProfiles() { 226 return StringUtils.toStringArray(doGetActiveProfiles()); 227 } 228 229 /** 230 * Return the set of active profiles as explicitly set through 231 * {@link #setActiveProfiles} or if the current set of active profiles 232 * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME} 233 * property and assign its value to the set of active profiles. 234 * @see #getActiveProfiles() 235 * @see #ACTIVE_PROFILES_PROPERTY_NAME 236 */ 237 protected Set<String> doGetActiveProfiles() { 238 synchronized (this.activeProfiles) { 239 if (this.activeProfiles.isEmpty()) { 240 String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME); 241 if (StringUtils.hasText(profiles)) { 242 setActiveProfiles(StringUtils.commaDelimitedListToStringArray( 243 StringUtils.trimAllWhitespace(profiles))); 244 } 245 } 246 return this.activeProfiles; 247 } 248 } 249 250 @Override 251 public void setActiveProfiles(String... profiles) { 252 Assert.notNull(profiles, "Profile array must not be null"); 253 if (logger.isDebugEnabled()) { 254 logger.debug("Activating profiles " + Arrays.asList(profiles)); 255 } 256 synchronized (this.activeProfiles) { 257 this.activeProfiles.clear(); 258 for (String profile : profiles) { 259 validateProfile(profile); 260 this.activeProfiles.add(profile); 261 } 262 } 263 } 264 265 @Override 266 public void addActiveProfile(String profile) { 267 if (logger.isDebugEnabled()) { 268 logger.debug("Activating profile '" + profile + "'"); 269 } 270 validateProfile(profile); 271 doGetActiveProfiles(); 272 synchronized (this.activeProfiles) { 273 this.activeProfiles.add(profile); 274 } 275 } 276 277 278 @Override 279 public String[] getDefaultProfiles() { 280 return StringUtils.toStringArray(doGetDefaultProfiles()); 281 } 282 283 /** 284 * Return the set of default profiles explicitly set via 285 * {@link #setDefaultProfiles(String...)} or if the current set of default profiles 286 * consists only of {@linkplain #getReservedDefaultProfiles() reserved default 287 * profiles}, then check for the presence of the 288 * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any) 289 * to the set of default profiles. 290 * @see #AbstractEnvironment() 291 * @see #getDefaultProfiles() 292 * @see #DEFAULT_PROFILES_PROPERTY_NAME 293 * @see #getReservedDefaultProfiles() 294 */ 295 protected Set<String> doGetDefaultProfiles() { 296 synchronized (this.defaultProfiles) { 297 if (this.defaultProfiles.equals(getReservedDefaultProfiles())) { 298 String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME); 299 if (StringUtils.hasText(profiles)) { 300 setDefaultProfiles(StringUtils.commaDelimitedListToStringArray( 301 StringUtils.trimAllWhitespace(profiles))); 302 } 303 } 304 return this.defaultProfiles; 305 } 306 } 307 308 /** 309 * Specify the set of profiles to be made active by default if no other profiles 310 * are explicitly made active through {@link #setActiveProfiles}. 311 * <p>Calling this method removes overrides any reserved default profiles 312 * that may have been added during construction of the environment. 313 * @see #AbstractEnvironment() 314 * @see #getReservedDefaultProfiles() 315 */ 316 @Override 317 public void setDefaultProfiles(String... profiles) { 318 Assert.notNull(profiles, "Profile array must not be null"); 319 synchronized (this.defaultProfiles) { 320 this.defaultProfiles.clear(); 321 for (String profile : profiles) { 322 validateProfile(profile); 323 this.defaultProfiles.add(profile); 324 } 325 } 326 } 327 328 @Override 329 public boolean acceptsProfiles(String... profiles) { 330 Assert.notEmpty(profiles, "Must specify at least one profile"); 331 for (String profile : profiles) { 332 if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') { 333 if (!isProfileActive(profile.substring(1))) { 334 return true; 335 } 336 } 337 else if (isProfileActive(profile)) { 338 return true; 339 } 340 } 341 return false; 342 } 343 344 /** 345 * Return whether the given profile is active, or if active profiles are empty 346 * whether the profile should be active by default. 347 * @throws IllegalArgumentException per {@link #validateProfile(String)} 348 */ 349 protected boolean isProfileActive(String profile) { 350 validateProfile(profile); 351 Set<String> currentActiveProfiles = doGetActiveProfiles(); 352 return (currentActiveProfiles.contains(profile) || 353 (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile))); 354 } 355 356 /** 357 * Validate the given profile, called internally prior to adding to the set of 358 * active or default profiles. 359 * <p>Subclasses may override to impose further restrictions on profile syntax. 360 * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or 361 * begins with the profile NOT operator (!). 362 * @see #acceptsProfiles 363 * @see #addActiveProfile 364 * @see #setDefaultProfiles 365 */ 366 protected void validateProfile(String profile) { 367 if (!StringUtils.hasText(profile)) { 368 throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text"); 369 } 370 if (profile.charAt(0) == '!') { 371 throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator"); 372 } 373 } 374 375 @Override 376 public MutablePropertySources getPropertySources() { 377 return this.propertySources; 378 } 379 380 @Override 381 @SuppressWarnings({"unchecked", "rawtypes"}) 382 public Map<String, Object> getSystemProperties() { 383 try { 384 return (Map) System.getProperties(); 385 } 386 catch (AccessControlException ex) { 387 return (Map) new ReadOnlySystemAttributesMap() { 388 @Override 389 protected String getSystemAttribute(String attributeName) { 390 try { 391 return System.getProperty(attributeName); 392 } 393 catch (AccessControlException ex) { 394 if (logger.isInfoEnabled()) { 395 logger.info("Caught AccessControlException when accessing system property '" + 396 attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); 397 } 398 return null; 399 } 400 } 401 }; 402 } 403 } 404 405 @Override 406 @SuppressWarnings({"unchecked", "rawtypes"}) 407 public Map<String, Object> getSystemEnvironment() { 408 if (suppressGetenvAccess()) { 409 return Collections.emptyMap(); 410 } 411 try { 412 return (Map) System.getenv(); 413 } 414 catch (AccessControlException ex) { 415 return (Map) new ReadOnlySystemAttributesMap() { 416 @Override 417 protected String getSystemAttribute(String attributeName) { 418 try { 419 return System.getenv(attributeName); 420 } 421 catch (AccessControlException ex) { 422 if (logger.isInfoEnabled()) { 423 logger.info("Caught AccessControlException when accessing system environment variable '" + 424 attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); 425 } 426 return null; 427 } 428 } 429 }; 430 } 431 } 432 433 /** 434 * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)} 435 * access for the purposes of {@link #getSystemEnvironment()}. 436 * <p>If this method returns {@code true}, an empty dummy Map will be used instead 437 * of the regular system environment Map, never even trying to call {@code getenv} 438 * and therefore avoiding security manager warnings (if any). 439 * <p>The default implementation checks for the "spring.getenv.ignore" system property, 440 * returning {@code true} if its value equals "true" in any case. 441 * @see #IGNORE_GETENV_PROPERTY_NAME 442 * @see SpringProperties#getFlag 443 */ 444 protected boolean suppressGetenvAccess() { 445 return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); 446 } 447 448 @Override 449 public void merge(ConfigurableEnvironment parent) { 450 for (PropertySource<?> ps : parent.getPropertySources()) { 451 if (!this.propertySources.contains(ps.getName())) { 452 this.propertySources.addLast(ps); 453 } 454 } 455 String[] parentActiveProfiles = parent.getActiveProfiles(); 456 if (!ObjectUtils.isEmpty(parentActiveProfiles)) { 457 synchronized (this.activeProfiles) { 458 for (String profile : parentActiveProfiles) { 459 this.activeProfiles.add(profile); 460 } 461 } 462 } 463 String[] parentDefaultProfiles = parent.getDefaultProfiles(); 464 if (!ObjectUtils.isEmpty(parentDefaultProfiles)) { 465 synchronized (this.defaultProfiles) { 466 this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME); 467 for (String profile : parentDefaultProfiles) { 468 this.defaultProfiles.add(profile); 469 } 470 } 471 } 472 } 473 474 475 //--------------------------------------------------------------------- 476 // Implementation of ConfigurablePropertyResolver interface 477 //--------------------------------------------------------------------- 478 479 @Override 480 public ConfigurableConversionService getConversionService() { 481 return this.propertyResolver.getConversionService(); 482 } 483 484 @Override 485 public void setConversionService(ConfigurableConversionService conversionService) { 486 this.propertyResolver.setConversionService(conversionService); 487 } 488 489 @Override 490 public void setPlaceholderPrefix(String placeholderPrefix) { 491 this.propertyResolver.setPlaceholderPrefix(placeholderPrefix); 492 } 493 494 @Override 495 public void setPlaceholderSuffix(String placeholderSuffix) { 496 this.propertyResolver.setPlaceholderSuffix(placeholderSuffix); 497 } 498 499 @Override 500 public void setValueSeparator(String valueSeparator) { 501 this.propertyResolver.setValueSeparator(valueSeparator); 502 } 503 504 @Override 505 public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { 506 this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders); 507 } 508 509 @Override 510 public void setRequiredProperties(String... requiredProperties) { 511 this.propertyResolver.setRequiredProperties(requiredProperties); 512 } 513 514 @Override 515 public void validateRequiredProperties() throws MissingRequiredPropertiesException { 516 this.propertyResolver.validateRequiredProperties(); 517 } 518 519 520 //--------------------------------------------------------------------- 521 // Implementation of PropertyResolver interface 522 //--------------------------------------------------------------------- 523 524 @Override 525 public boolean containsProperty(String key) { 526 return this.propertyResolver.containsProperty(key); 527 } 528 529 @Override 530 public String getProperty(String key) { 531 return this.propertyResolver.getProperty(key); 532 } 533 534 @Override 535 public String getProperty(String key, String defaultValue) { 536 return this.propertyResolver.getProperty(key, defaultValue); 537 } 538 539 @Override 540 public <T> T getProperty(String key, Class<T> targetType) { 541 return this.propertyResolver.getProperty(key, targetType); 542 } 543 544 @Override 545 public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { 546 return this.propertyResolver.getProperty(key, targetType, defaultValue); 547 } 548 549 @Override 550 @Deprecated 551 public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) { 552 return this.propertyResolver.getPropertyAsClass(key, targetType); 553 } 554 555 @Override 556 public String getRequiredProperty(String key) throws IllegalStateException { 557 return this.propertyResolver.getRequiredProperty(key); 558 } 559 560 @Override 561 public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException { 562 return this.propertyResolver.getRequiredProperty(key, targetType); 563 } 564 565 @Override 566 public String resolvePlaceholders(String text) { 567 return this.propertyResolver.resolvePlaceholders(text); 568 } 569 570 @Override 571 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { 572 return this.propertyResolver.resolveRequiredPlaceholders(text); 573 } 574 575 576 @Override 577 public String toString() { 578 return getClass().getSimpleName() + " {activeProfiles=" + this.activeProfiles + 579 ", defaultProfiles=" + this.defaultProfiles + ", propertySources=" + this.propertySources + "}"; 580 } 581 582}