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