View Javadoc
1   /*
2    * Copyright 2002-2013 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.util.HashMap;
20  import java.util.Map;
21  import java.util.Properties;
22  
23  import org.junit.Before;
24  import org.junit.Test;
25  
26  import org.springframework.core.convert.ConversionException;
27  import org.springframework.mock.env.MockPropertySource;
28  
29  import static org.hamcrest.Matchers.*;
30  import static org.junit.Assert.*;
31  
32  /**
33   * @author Chris Beams
34   * @since 3.1
35   */
36  public class PropertySourcesPropertyResolverTests {
37  
38  	private Properties testProperties;
39  
40  	private MutablePropertySources propertySources;
41  
42  	private ConfigurablePropertyResolver propertyResolver;
43  
44  
45  	@Before
46  	public void setUp() {
47  		propertySources = new MutablePropertySources();
48  		propertyResolver = new PropertySourcesPropertyResolver(propertySources);
49  		testProperties = new Properties();
50  		propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties));
51  	}
52  
53  
54  	@Test
55  	public void containsProperty() {
56  		assertThat(propertyResolver.containsProperty("foo"), is(false));
57  		testProperties.put("foo", "bar");
58  		assertThat(propertyResolver.containsProperty("foo"), is(true));
59  	}
60  
61  	@Test
62  	public void getProperty() {
63  		assertThat(propertyResolver.getProperty("foo"), nullValue());
64  		testProperties.put("foo", "bar");
65  		assertThat(propertyResolver.getProperty("foo"), is("bar"));
66  	}
67  
68  	@Test
69  	public void getProperty_withDefaultValue() {
70  		assertThat(propertyResolver.getProperty("foo", "myDefault"), is("myDefault"));
71  		testProperties.put("foo", "bar");
72  		assertThat(propertyResolver.getProperty("foo"), is("bar"));
73  	}
74  
75  	@Test
76  	public void getProperty_propertySourceSearchOrderIsFIFO() {
77  		MutablePropertySources sources = new MutablePropertySources();
78  		PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
79  		sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value"));
80  		assertThat(resolver.getProperty("pName"), equalTo("ps1Value"));
81  		sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value"));
82  		assertThat(resolver.getProperty("pName"), equalTo("ps2Value"));
83  		sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value"));
84  		assertThat(resolver.getProperty("pName"), equalTo("ps3Value"));
85  	}
86  
87  	@Test
88  	public void getProperty_withExplicitNullValue() {
89  		// java.util.Properties does not allow null values (because Hashtable does not)
90  		Map<String, Object> nullableProperties = new HashMap<String, Object>();
91  		propertySources.addLast(new MapPropertySource("nullableProperties", nullableProperties));
92  		nullableProperties.put("foo", null);
93  		assertThat(propertyResolver.getProperty("foo"), nullValue());
94  	}
95  
96  	@Test
97  	public void getProperty_withTargetType_andDefaultValue() {
98  		assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(42));
99  		testProperties.put("foo", 13);
100 		assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(13));
101 	}
102 
103 	@Test
104 	public void getProperty_withStringArrayConversion() {
105 		testProperties.put("foo", "bar,baz");
106 		assertThat(propertyResolver.getProperty("foo", String[].class), equalTo(new String[] { "bar", "baz" }));
107 	}
108 
109 	@Test
110 	public void getProperty_withNonConvertibleTargetType() {
111 		testProperties.put("foo", "bar");
112 
113 		class TestType { }
114 
115 		try {
116 			propertyResolver.getProperty("foo", TestType.class);
117 			fail("Expected IllegalArgumentException due to non-convertible types");
118 		}
119 		catch (IllegalArgumentException ex) {
120 			// expected
121 		}
122 	}
123 
124 	@Test
125 	public void getProperty_doesNotCache_replaceExistingKeyPostConstruction() {
126 		String key = "foo";
127 		String value1 = "bar";
128 		String value2 = "biz";
129 
130 		HashMap<String, Object> map = new HashMap<String, Object>();
131 		map.put(key, value1); // before construction
132 		MutablePropertySources propertySources = new MutablePropertySources();
133 		propertySources.addFirst(new MapPropertySource("testProperties", map));
134 		PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
135 		assertThat(propertyResolver.getProperty(key), equalTo(value1));
136 		map.put(key, value2); // after construction and first resolution
137 		assertThat(propertyResolver.getProperty(key), equalTo(value2));
138 	}
139 
140 	@Test
141 	public void getProperty_doesNotCache_addNewKeyPostConstruction() {
142 		HashMap<String, Object> map = new HashMap<String, Object>();
143 		MutablePropertySources propertySources = new MutablePropertySources();
144 		propertySources.addFirst(new MapPropertySource("testProperties", map));
145 		PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
146 		assertThat(propertyResolver.getProperty("foo"), equalTo(null));
147 		map.put("foo", "42");
148 		assertThat(propertyResolver.getProperty("foo"), equalTo("42"));
149 	}
150 
151 	@Test
152 	public void getPropertySources_replacePropertySource() {
153 		propertySources = new MutablePropertySources();
154 		propertyResolver = new PropertySourcesPropertyResolver(propertySources);
155 		propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue"));
156 		propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue"));
157 
158 		// 'local' was added first so has precedence
159 		assertThat(propertyResolver.getProperty("foo"), equalTo("localValue"));
160 
161 		// replace 'local' with new property source
162 		propertySources.replace("local", new MockPropertySource("new").withProperty("foo", "newValue"));
163 
164 		// 'system' now has precedence
165 		assertThat(propertyResolver.getProperty("foo"), equalTo("newValue"));
166 
167 		assertThat(propertySources.size(), is(2));
168 	}
169 
170 	@Test
171 	public void getRequiredProperty() {
172 		testProperties.put("exists", "xyz");
173 		assertThat(propertyResolver.getRequiredProperty("exists"), is("xyz"));
174 
175 		try {
176 			propertyResolver.getRequiredProperty("bogus");
177 			fail("expected IllegalStateException");
178 		}
179 		catch (IllegalStateException ex) {
180 			// expected
181 		}
182 	}
183 
184 	@Test
185 	public void getRequiredProperty_withStringArrayConversion() {
186 		testProperties.put("exists", "abc,123");
187 		assertThat(propertyResolver.getRequiredProperty("exists", String[].class), equalTo(new String[] { "abc", "123" }));
188 
189 		try {
190 			propertyResolver.getRequiredProperty("bogus", String[].class);
191 			fail("expected IllegalStateException");
192 		}
193 		catch (IllegalStateException ex) {
194 			// expected
195 		}
196 	}
197 
198 	@Test
199 	public void resolvePlaceholders() {
200 		MutablePropertySources propertySources = new MutablePropertySources();
201 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
202 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
203 		assertThat(resolver.resolvePlaceholders("Replace this ${key}"), equalTo("Replace this value"));
204 	}
205 
206 	@Test
207 	public void resolvePlaceholders_withUnresolvable() {
208 		MutablePropertySources propertySources = new MutablePropertySources();
209 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
210 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
211 		assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"),
212 				equalTo("Replace this value plus ${unknown}"));
213 	}
214 
215 	@Test
216 	public void resolvePlaceholders_withDefaultValue() {
217 		MutablePropertySources propertySources = new MutablePropertySources();
218 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
219 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
220 		assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
221 				equalTo("Replace this value plus defaultValue"));
222 	}
223 
224 	@Test(expected=IllegalArgumentException.class)
225 	public void resolvePlaceholders_withNullInput() {
226 		new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null);
227 	}
228 
229 	@Test
230 	public void resolveRequiredPlaceholders() {
231 		MutablePropertySources propertySources = new MutablePropertySources();
232 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
233 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
234 		assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}"), equalTo("Replace this value"));
235 	}
236 
237 	@Test(expected=IllegalArgumentException.class)
238 	public void resolveRequiredPlaceholders_withUnresolvable() {
239 		MutablePropertySources propertySources = new MutablePropertySources();
240 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
241 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
242 		resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}");
243 	}
244 
245 	@Test
246 	public void resolveRequiredPlaceholders_withDefaultValue() {
247 		MutablePropertySources propertySources = new MutablePropertySources();
248 		propertySources.addFirst(new MockPropertySource().withProperty("key", "value"));
249 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
250 		assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"),
251 				equalTo("Replace this value plus defaultValue"));
252 	}
253 
254 	@Test(expected=IllegalArgumentException.class)
255 	public void resolveRequiredPlaceholders_withNullInput() {
256 		new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null);
257 	}
258 
259 	@Test
260 	public void getPropertyAsClass() throws ClassNotFoundException, LinkageError {
261 		MutablePropertySources propertySources = new MutablePropertySources();
262 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class.getName()));
263 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
264 		assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class));
265 	}
266 
267 	@Test
268 	public void getPropertyAsClass_withInterfaceAsTarget() throws ClassNotFoundException, LinkageError {
269 		MutablePropertySources propertySources = new MutablePropertySources();
270 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", SomeType.class.getName()));
271 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
272 		assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SomeType.class));
273 	}
274 
275 	@Test(expected=ConversionException.class)
276 	public void getPropertyAsClass_withMismatchedTypeForValue() {
277 		MutablePropertySources propertySources = new MutablePropertySources();
278 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", "java.lang.String"));
279 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
280 		resolver.getPropertyAsClass("some.class", SomeType.class);
281 	}
282 
283 	@Test(expected=ConversionException.class)
284 	public void getPropertyAsClass_withNonExistentClassForValue() {
285 		MutablePropertySources propertySources = new MutablePropertySources();
286 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", "some.bogus.Class"));
287 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
288 		resolver.getPropertyAsClass("some.class", SomeType.class);
289 	}
290 
291 	@Test
292 	public void getPropertyAsClass_withObjectForValue() {
293 		MutablePropertySources propertySources = new MutablePropertySources();
294 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", new SpecificType()));
295 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
296 		assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class));
297 	}
298 
299 	@Test(expected=ConversionException.class)
300 	public void getPropertyAsClass_withMismatchedObjectForValue() {
301 		MutablePropertySources propertySources = new MutablePropertySources();
302 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", new Integer(42)));
303 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
304 		resolver.getPropertyAsClass("some.class", SomeType.class);
305 	}
306 
307 	@Test
308 	public void getPropertyAsClass_withRealClassForValue() {
309 		MutablePropertySources propertySources = new MutablePropertySources();
310 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class));
311 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
312 		assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class));
313 	}
314 
315 	@Test(expected=ConversionException.class)
316 	public void getPropertyAsClass_withMismatchedRealClassForValue() {
317 		MutablePropertySources propertySources = new MutablePropertySources();
318 		propertySources.addFirst(new MockPropertySource().withProperty("some.class", Integer.class));
319 		PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
320 		resolver.getPropertyAsClass("some.class", SomeType.class);
321 	}
322 
323 	@Test
324 	public void setRequiredProperties_andValidateRequiredProperties() {
325 		// no properties have been marked as required -> validation should pass
326 		propertyResolver.validateRequiredProperties();
327 
328 		// mark which properties are required
329 		propertyResolver.setRequiredProperties("foo", "bar");
330 
331 		// neither foo nor bar properties are present -> validating should throw
332 		try {
333 			propertyResolver.validateRequiredProperties();
334 			fail("expected validation exception");
335 		}
336 		catch (MissingRequiredPropertiesException ex) {
337 			assertThat(ex.getMessage(), equalTo(
338 					"The following properties were declared as required " +
339 					"but could not be resolved: [foo, bar]"));
340 		}
341 
342 		// add foo property -> validation should fail only on missing 'bar' property
343 		testProperties.put("foo", "fooValue");
344 		try {
345 			propertyResolver.validateRequiredProperties();
346 			fail("expected validation exception");
347 		}
348 		catch (MissingRequiredPropertiesException ex) {
349 			assertThat(ex.getMessage(), equalTo(
350 					"The following properties were declared as required " +
351 					"but could not be resolved: [bar]"));
352 		}
353 
354 		// add bar property -> validation should pass, even with an empty string value
355 		testProperties.put("bar", "");
356 		propertyResolver.validateRequiredProperties();
357 	}
358 
359 	@Test
360 	public void resolveNestedPropertyPlaceholders() {
361 		MutablePropertySources ps = new MutablePropertySources();
362 		ps.addFirst(new MockPropertySource()
363 			.withProperty("p1", "v1")
364 			.withProperty("p2", "v2")
365 			.withProperty("p3", "${p1}:${p2}")              // nested placeholders
366 			.withProperty("p4", "${p3}")                    // deeply nested placeholders
367 			.withProperty("p5", "${p1}:${p2}:${bogus}")     // unresolvable placeholder
368 			.withProperty("p6", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default
369 			.withProperty("pL", "${pR}")                    // cyclic reference left
370 			.withProperty("pR", "${pL}")                    // cyclic reference right
371 		);
372 		ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
373 		assertThat(pr.getProperty("p1"), equalTo("v1"));
374 		assertThat(pr.getProperty("p2"), equalTo("v2"));
375 		assertThat(pr.getProperty("p3"), equalTo("v1:v2"));
376 		assertThat(pr.getProperty("p4"), equalTo("v1:v2"));
377 		try {
378 			pr.getProperty("p5");
379 		}
380 		catch (IllegalArgumentException ex) {
381 			assertThat(ex.getMessage(), containsString(
382 					"Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\""));
383 		}
384 		assertThat(pr.getProperty("p6"), equalTo("v1:v2:def"));
385 		try {
386 			pr.getProperty("pL");
387 		}
388 		catch (IllegalArgumentException ex) {
389 			assertTrue(ex.getMessage().toLowerCase().contains("circular"));
390 		}
391 	}
392 
393 	@Test
394 	public void ignoreUnresolvableNestedPlaceholdersIsConfigurable() {
395 		MutablePropertySources ps = new MutablePropertySources();
396 		ps.addFirst(new MockPropertySource()
397 			.withProperty("p1", "v1")
398 			.withProperty("p2", "v2")
399 			.withProperty("p3", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default
400 			.withProperty("p4", "${p1}:${p2}:${bogus}")     // unresolvable placeholder
401 		);
402 		ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps);
403 		assertThat(pr.getProperty("p1"), equalTo("v1"));
404 		assertThat(pr.getProperty("p2"), equalTo("v2"));
405 		assertThat(pr.getProperty("p3"), equalTo("v1:v2:def"));
406 
407 		// placeholders nested within the value of "p4" are unresolvable and cause an
408 		// exception by default
409 		try {
410 			pr.getProperty("p4");
411 		}
412 		catch (IllegalArgumentException ex) {
413 			assertThat(ex.getMessage(), containsString(
414 					"Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\""));
415 		}
416 
417 		// relax the treatment of unresolvable nested placeholders
418 		pr.setIgnoreUnresolvableNestedPlaceholders(true);
419 		// and observe they now pass through unresolved
420 		assertThat(pr.getProperty("p4"), equalTo("v1:v2:${bogus}"));
421 
422 		// resolve[Nested]Placeholders methods behave as usual regardless the value of
423 		// ignoreUnresolvableNestedPlaceholders
424 		assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}"), equalTo("v1:v2:${bogus}"));
425 		try {
426 			pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}");
427 		}
428 		catch (IllegalArgumentException ex) {
429 			assertThat(ex.getMessage(), containsString(
430 					"Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\""));
431 		}
432 	}
433 
434 
435 	interface SomeType {
436 	}
437 
438 	static class SpecificType implements SomeType {
439 	}
440 
441 }