Skip to content

Commit 06e34f0

Browse files
committed
Allow PropertyResolvers to ignore unresolvable ${placeholders}
Prior to this commit, the PropertyResolver API (and therefore the Environment API) allowed callers a choice between #resolvePlaceholders and #resolveRequiredPlaceholders for low-level ${placeholder} resolution. However, when calling the higher level #getProperty variants, users had no control over whether property values returned with unresolvable ${placeholders} would result in an exception or simply be passed through. This commit introduces a #setIgnoreUnresolvableNestedPlaceholders property via ConfigurablePropertyResolver, defaulting to false, the value of which is respected by AbstractPropertyResolver#getProperty method implementations. See the new test in PropertySourcesPropertyResolverTests for usage examples. Issue: SPR-9569, SPR-9473
1 parent 01272fb commit 06e34f0

File tree

5 files changed

+83
-6
lines changed

5 files changed

+83
-6
lines changed

spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentExc
464464
return this.propertyResolver.resolveRequiredPlaceholders(text);
465465
}
466466

467+
public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
468+
this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders);
469+
}
470+
467471
public void setConversionService(ConfigurableConversionService conversionService) {
468472
this.propertyResolver.setConversionService(conversionService);
469473
}

spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
4545

4646
private PropertyPlaceholderHelper nonStrictHelper;
4747
private PropertyPlaceholderHelper strictHelper;
48+
private boolean ignoreUnresolvableNestedPlaceholders = false;
4849

4950
private String placeholderPrefix = PLACEHOLDER_PREFIX;
5051
private String placeholderSuffix = PLACEHOLDER_SUFFIX;
@@ -142,6 +143,28 @@ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentExc
142143
return doResolvePlaceholders(text, strictHelper);
143144
}
144145

146+
/**
147+
* {@inheritDoc}
148+
* <p>The default value for this implementation is {@code false}.
149+
* @since 3.2
150+
*/
151+
public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
152+
this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
153+
}
154+
155+
/**
156+
* Resolve placeholders within the given string, deferring to the value of
157+
* {@link #setIgnoreUnresolvableNestedPlaceholders(boolean)} to determine whether any
158+
* unresolvable placeholders should raise an exception or be ignored.
159+
* @since 3.2
160+
* @see #setIgnoreUnresolvableNestedPlaceholders(boolean)
161+
*/
162+
protected String resolveNestedPlaceholders(String value) {
163+
return this.ignoreUnresolvableNestedPlaceholders ?
164+
this.resolvePlaceholders(value) :
165+
this.resolveRequiredPlaceholders(value);
166+
}
167+
145168
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
146169
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
147170
this.valueSeparator, ignoreUnresolvablePlaceholders);

spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -87,4 +87,17 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
8787
* properties are not resolvable.
8888
*/
8989
void validateRequiredProperties() throws MissingRequiredPropertiesException;
90+
91+
/**
92+
* Set whether to throw an exception when encountering an unresolvable placeholder
93+
* nested within the value of a given property. A {@code false} value indicates strict
94+
* resolution, i.e. that an exception will be thrown. A {@code true} value indicates
95+
* that unresolvable nested placeholders should be passed through in their unresolved
96+
* ${...} form.
97+
* <p>Implementations of {@link #getProperty(String)} and its variants must inspect
98+
* the value set here to determine correct behavior when property values contain
99+
* unresolvable placeholders.
100+
* @since 3.2
101+
*/
102+
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
90103
}

spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public <T> T getProperty(String key, Class<T> targetValueType) {
7373
if ((value = propertySource.getProperty(key)) != null) {
7474
Class<?> valueType = value.getClass();
7575
if (String.class.equals(valueType)) {
76-
value = this.resolveRequiredPlaceholders((String) value);
76+
value = this.resolveNestedPlaceholders((String) value);
7777
}
7878
if (debugEnabled) {
7979
logger.debug(

spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
import java.util.Map;
2121
import java.util.Properties;
2222

23-
import org.hamcrest.CoreMatchers;
24-
2523
import org.junit.Before;
2624
import org.junit.Test;
2725

@@ -364,7 +362,7 @@ public void resolveNestedPropertyPlaceholders() {
364362
.withProperty("pL", "${pR}") // cyclic reference left
365363
.withProperty("pR", "${pL}") // cyclic reference right
366364
);
367-
PropertySourcesPropertyResolver pr = new PropertySourcesPropertyResolver(ps);
365+
ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
368366
assertThat(pr.getProperty("p1"), equalTo("v1"));
369367
assertThat(pr.getProperty("p2"), equalTo("v2"));
370368
assertThat(pr.getProperty("p3"), equalTo("v1:v2"));
@@ -383,6 +381,45 @@ public void resolveNestedPropertyPlaceholders() {
383381
}
384382
}
385383

384+
@Test
385+
public void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
386+
MutablePropertySources ps = new MutablePropertySources();
387+
ps.addFirst(new MockPropertySource()
388+
.withProperty("p1", "v1")
389+
.withProperty("p2", "v2")
390+
.withProperty("p3", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default
391+
.withProperty("p4", "${p1}:${p2}:${bogus}") // unresolvable placeholder
392+
);
393+
ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
394+
assertThat(pr.getProperty("p1"), equalTo("v1"));
395+
assertThat(pr.getProperty("p2"), equalTo("v2"));
396+
assertThat(pr.getProperty("p3"), equalTo("v1:v2:def"));
397+
398+
// placeholders nested within the value of "p4" are unresolvable and cause an
399+
// exception by default
400+
try {
401+
pr.getProperty("p4");
402+
} catch (IllegalArgumentException ex) {
403+
assertThat(ex.getMessage(), containsString(
404+
"Could not resolve placeholder 'bogus' in string value [${p1}:${p2}:${bogus}]"));
405+
}
406+
407+
// relax the treatment of unresolvable nested placeholders
408+
pr.setIgnoreUnresolvableNestedPlaceholders(true);
409+
// and observe they now pass through unresolved
410+
assertThat(pr.getProperty("p4"), equalTo("v1:v2:${bogus}"));
411+
412+
// resolve[Nested]Placeholders methods behave as usual regardless the value of
413+
// ignoreUnresolvableNestedPlaceholders
414+
assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}"), equalTo("v1:v2:${bogus}"));
415+
try {
416+
pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}");
417+
} catch (IllegalArgumentException ex) {
418+
assertThat(ex.getMessage(), containsString(
419+
"Could not resolve placeholder 'bogus' in string value [${p1}:${p2}:${bogus}]"));
420+
}
421+
}
422+
386423

387424
static interface SomeType { }
388425
static class SpecificType implements SomeType { }

0 commit comments

Comments
 (0)