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