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 * @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 * @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 * @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*</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