/*
* Copyright 2002-2017 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
*
* http://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.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.mock.env.MockPropertySource;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* @author Chris Beams
* @since 3.1
*/
public class PropertySourcesPropertyResolverTests {
private Properties testProperties;
private MutablePropertySources propertySources;
private ConfigurablePropertyResolver propertyResolver;
@Before
public void setUp() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
testProperties = new Properties();
propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
}
@Test
public void containsProperty() {
assertThat(propertyResolver.containsProperty("foo"), is(false));
testProperties.put("foo", "bar");
assertThat(propertyResolver.containsProperty("foo"), is(true));
}
@Test
public void getProperty() {
assertThat(propertyResolver.getProperty("foo"), nullValue());
testProperties.put("foo", "bar");
assertThat(propertyResolver.getProperty("foo"), is("bar"));
}
@Test
public void getProperty_withDefaultValue() {
assertThat(propertyResolver.getProperty("foo", "myDefault"), is("myDefault"));
testProperties.put("foo", "bar");
assertThat(propertyResolver.getProperty("foo"), is("bar"));
}
@Test
public void getProperty_propertySourceSearchOrderIsFIFO() {
MutablePropertySources sources = new MutablePropertySources();
PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps1Value"));
sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps2Value"));
sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
assertThat(resolver.getProperty("pName"), equalTo("ps3Value"));
}
@Test
public void getProperty_withExplicitNullValue() {
// java.util.Properties does not allow null values (because Hashtable does not)
Map<String, Object> nullableProperties = new HashMap<>();
propertySources.addLast(new MapPropertySource("nullableProperties", nullableProperties));
nullableProperties.put("foo", null);
assertThat(propertyResolver.getProperty("foo"), nullValue());
}
@Test
public void getProperty_withTargetType_andDefaultValue() {
assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(42));
testProperties.put("foo", 13);
assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(13));
}
@Test
public void getProperty_withStringArrayConversion() {
testProperties.put("foo", "bar,baz");
assertThat(propertyResolver.getProperty("foo", String[].class), equalTo(new String[] { "bar", "baz" }));
}
@Test
public void getProperty_withNonConvertibleTargetType() {
testProperties.put("foo", "bar");
class TestType { }
try {
propertyResolver.getProperty("foo", TestType.class);
fail("Expected ConverterNotFoundException due to non-convertible types");
}
catch (ConverterNotFoundException ex) {
// expected
}
}
@Test
public void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
String key = "foo";
String value1 = "bar";
String value2 = "biz";
HashMap<String, Object> map = new HashMap<>();
map.put(key, value1); // before construction
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty(key), equalTo(value1));
map.put(key, value2); // after construction and first resolution
assertThat(propertyResolver.getProperty(key), equalTo(value2));
}
@Test
public void getProperty_doesNotCache_addNewKeyPostConstruction() {
HashMap<String, Object> map = new HashMap<>();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("testProperties", map));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(propertyResolver.getProperty("foo"), equalTo(null));
map.put("foo", "42");
assertThat(propertyResolver.getProperty("foo"), equalTo("42"));
}
@Test
public void getPropertySources_replacePropertySource() {
propertySources = new MutablePropertySources();
propertyResolver = new PropertySourcesPropertyResolver(propertySources);
propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue"));
propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue"));
// 'local' was added first so has precedence
assertThat(propertyResolver.getProperty("foo"), equalTo("localValue"));
// replace 'local' with new property source
propertySources.replace("local", new MockPropertySource("new").withProperty("foo", "newValue"));
// 'system' now has precedence
assertThat(propertyResolver.getProperty("foo"), equalTo("newValue"));
assertThat(propertySources.size(), is(2));
}
@Test
public void getRequiredProperty() {
testProperties.put("exists", "xyz");
assertThat(propertyResolver.getRequiredProperty("exists"), is("xyz"));
try {
propertyResolver.getRequiredProperty("bogus");
fail("expected IllegalStateException");
}
catch (IllegalStateException ex) {
// expected
}
}
@Test
public void getRequiredProperty_withStringArrayConversion() {
testProperties.put("exists", "abc,123");
assertThat(propertyResolver.getRequiredProperty("exists", String[].class), equalTo(new String[] { "abc", "123" }));
try {
propertyResolver.getRequiredProperty("bogus", String[].class);
fail("expected IllegalStateException");
}
catch (IllegalStateException ex) {
// expected
}
}
@Test
public void resolvePlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key}"), equalTo("Replace this value"));
}
@Test
public void resolvePlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"),
equalTo("Replace this value plus ${unknown}"));
}
@Test
public void resolvePlaceholders_withDefaultValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
equalTo("Replace this value plus defaultValue"));
}
@Test(expected = IllegalArgumentException.class)
public void resolvePlaceholders_withNullInput() {
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null);
}
@Test
public void resolveRequiredPlaceholders() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}"), equalTo("Replace this value"));
}
@Test(expected = IllegalArgumentException.class)
public void resolveRequiredPlaceholders_withUnresolvable() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}");
}
@Test
public void resolveRequiredPlaceholders_withDefaultValue() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
equalTo("Replace this value plus defaultValue"));
}
@Test(expected = IllegalArgumentException.class)
public void resolveRequiredPlaceholders_withNullInput() {
new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null);
}
@Test
public void setRequiredProperties_andValidateRequiredProperties() {
// no properties have been marked as required -> validation should pass
propertyResolver.validateRequiredProperties();
// mark which properties are required
propertyResolver.setRequiredProperties("foo", "bar");
// neither foo nor bar properties are present -> validating should throw
try {
propertyResolver.validateRequiredProperties();
fail("expected validation exception");
}
catch (MissingRequiredPropertiesException ex) {
assertThat(ex.getMessage(), equalTo(
"The following properties were declared as required " +
"but could not be resolved: [foo, bar]"));
}
// add foo property -> validation should fail only on missing 'bar' property
testProperties.put("foo", "fooValue");
try {
propertyResolver.validateRequiredProperties();
fail("expected validation exception");
}
catch (MissingRequiredPropertiesException ex) {
assertThat(ex.getMessage(), equalTo(
"The following properties were declared as required " +
"but could not be resolved: [bar]"));
}
// add bar property -> validation should pass, even with an empty string value
testProperties.put("bar", "");
propertyResolver.validateRequiredProperties();
}
@Test
public void resolveNestedPropertyPlaceholders() {
MutablePropertySources ps = new MutablePropertySources();
ps.addFirst(new MockPropertySource()
.withProperty("p1", "v1")
.withProperty("p2", "v2")
.withProperty("p3", "${p1}:${p2}") // nested placeholders
.withProperty("p4", "${p3}") // deeply nested placeholders
.withProperty("p5", "${p1}:${p2}:${bogus}") // unresolvable placeholder
.withProperty("p6", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default
.withProperty("pL", "${pR}") // cyclic reference left
.withProperty("pR", "${pL}") // cyclic reference right
);
ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
assertThat(pr.getProperty("p1"), equalTo("v1"));
assertThat(pr.getProperty("p2"), equalTo("v2"));
assertThat(pr.getProperty("p3"), equalTo("v1:v2"));
assertThat(pr.getProperty("p4"), equalTo("v1:v2"));
try {
pr.getProperty("p5");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString(
"Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""));
}
assertThat(pr.getProperty("p6"), equalTo("v1:v2:def"));
try {
pr.getProperty("pL");
}
catch (IllegalArgumentException ex) {
assertTrue(ex.getMessage().toLowerCase().contains("circular"));
}
}
@Test
public void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
MutablePropertySources ps = new MutablePropertySources();
ps.addFirst(new MockPropertySource()
.withProperty("p1", "v1")
.withProperty("p2", "v2")
.withProperty("p3", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default
.withProperty("p4", "${p1}:${p2}:${bogus}") // unresolvable placeholder
);
ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
assertThat(pr.getProperty("p1"), equalTo("v1"));
assertThat(pr.getProperty("p2"), equalTo("v2"));
assertThat(pr.getProperty("p3"), equalTo("v1:v2:def"));
// placeholders nested within the value of "p4" are unresolvable and cause an
// exception by default
try {
pr.getProperty("p4");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString(
"Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""));
}
// relax the treatment of unresolvable nested placeholders
pr.setIgnoreUnresolvableNestedPlaceholders(true);
// and observe they now pass through unresolved
assertThat(pr.getProperty("p4"), equalTo("v1:v2:${bogus}"));
// resolve[Nested]Placeholders methods behave as usual regardless the value of
// ignoreUnresolvableNestedPlaceholders
assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}"), equalTo("v1:v2:${bogus}"));
try {
pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString(
"Could not resolve placeholder 'bogus' in value \"${p1}:${p2}:${bogus}\""));
}
}
}