/* * Copyright 2012-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.boot.context.properties; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.validation.constraints.NotNull; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.core.env.MutablePropertySources; import org.springframework.stereotype.Component; import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.validation.annotation.Validated; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link EnableConfigurationProperties}. * * @author Dave Syer * @author Stephane Nicoll * @author Madhura Bhave */ public class EnableConfigurationPropertiesTests { private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @Rule public ExpectedException thrown = ExpectedException.none(); @After public void close() { System.clearProperty("name"); System.clearProperty("nested.name"); System.clearProperty("nested_name"); } @Test public void testBasicPropertiesBinding() { this.context.register(TestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1); assertThat(this.context.containsBean(TestProperties.class.getName())).isTrue(); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo"); } @Test public void testSystemPropertiesBinding() { this.context.register(TestConfiguration.class); System.setProperty("name", "foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo"); } @Test public void testNestedSystemPropertiesBinding() { this.context.register(NestedConfiguration.class); System.setProperty("name", "foo"); System.setProperty("nested.name", "bar"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(NestedProperties.class)).hasSize(1); assertThat(this.context.getBean(NestedProperties.class).name).isEqualTo("foo"); assertThat(this.context.getBean(NestedProperties.class).nested.name) .isEqualTo("bar"); } @Test public void testStrictPropertiesBinding() { removeSystemProperties(); this.context.register(StrictTestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(StrictTestProperties.class)) .hasSize(1); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo"); } @Test public void testPropertiesEmbeddedBinding() { this.context.register(EmbeddedTestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "spring.foo.name=foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(EmbeddedTestProperties.class)) .hasSize(1); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo"); } @Test public void testExceptionOnValidation() { this.context.register(ExceptionIfInvalidTestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name:foo"); this.thrown.expectCause(Matchers.<Throwable>instanceOf(BindException.class)); this.context.refresh(); } @Test public void testNoExceptionOnValidationWithoutValidated() { this.context.register(IgnoredIfInvalidButNotValidatedTestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name:foo"); this.context.refresh(); IgnoredIfInvalidButNotValidatedTestProperties bean = this.context .getBean(IgnoredIfInvalidButNotValidatedTestProperties.class); assertThat(bean.getDescription()).isNull(); } @Test public void testNestedPropertiesBinding() { this.context.register(NestedConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo", "nested.name=bar"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(NestedProperties.class)).hasSize(1); assertThat(this.context.getBean(NestedProperties.class).name).isEqualTo("foo"); assertThat(this.context.getBean(NestedProperties.class).nested.name) .isEqualTo("bar"); } @Test public void testBasicPropertiesBindingWithAnnotationOnBaseClass() { this.context.register(DerivedConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(DerivedProperties.class)).hasSize(1); assertThat(this.context.getBean(BaseProperties.class).name).isEqualTo("foo"); } @Test public void testArrayPropertiesBinding() { this.context.register(TestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo", "array=1,2,3"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1); assertThat(this.context.getBean(TestProperties.class).getArray()).hasSize(3); } @Test public void testCollectionPropertiesBindingFromYamlArray() { this.context.register(TestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo", "list[0]=1", "list[1]=2"); this.context.refresh(); assertThat(this.context.getBean(TestProperties.class).getList()).hasSize(2); } @Test public void testCollectionPropertiesBindingWithOver256Elements() { this.context.register(TestConfiguration.class); List<String> pairs = new ArrayList<>(); pairs.add("name:foo"); for (int i = 0; i < 1000; i++) { pairs.add("list[" + i + "]:" + i); } TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, pairs.toArray(new String[] {})); this.context.refresh(); assertThat(this.context.getBean(TestProperties.class).getList()).hasSize(1000); } @Test public void testPropertiesBindingWithoutAnnotation() { this.context.register(InvalidConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name:foo"); this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("No ConfigurationProperties annotation found"); this.context.refresh(); } @Test public void testPropertiesBindingWithoutAnnotationValue() { this.context.register(MoreConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=foo"); this.context.refresh(); assertThat(this.context.getBeanNamesForType(MoreProperties.class)).hasSize(1); assertThat(this.context.getBean(MoreProperties.class).name).isEqualTo("foo"); } @Test public void testPropertiesBindingWithDefaultsInXml() { this.context.register(TestConfiguration.class, DefaultXmlConfiguration.class); this.context.refresh(); String[] beanNames = this.context.getBeanNamesForType(TestProperties.class); assertThat(beanNames).as("Wrong beans").containsExactly(beanNames); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("bar"); } @Test public void testPropertiesBindingWithDefaultsInBeanMethod() { this.context.register(DefaultConfiguration.class); this.context.refresh(); String[] beanNames = this.context.getBeanNamesForType(TestProperties.class); assertThat(beanNames).as("Wrong beans").containsExactly(beanNames); assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("bar"); } @Test public void testBindingWithTwoBeans() { this.context.register(MoreConfiguration.class, TestConfiguration.class); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class).length) .isEqualTo(1); assertThat(this.context.getBeanNamesForType(MoreProperties.class).length) .isEqualTo(1); } @Test public void testBindingWithParentContext() { AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); parent.register(TestConfiguration.class); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(parent, "name=parent"); parent.refresh(); this.context.setParent(parent); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "name=child"); this.context.register(TestConfiguration.class, TestConsumer.class); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class).length) .isEqualTo(0); assertThat(parent.getBeanNamesForType(TestProperties.class).length).isEqualTo(1); assertThat(this.context.getBean(TestConsumer.class).getName()) .isEqualTo("parent"); parent.close(); } @Test public void testBindingOnlyParentContext() { AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(parent, "name=foo"); parent.register(TestConfiguration.class); parent.refresh(); this.context.setParent(parent); this.context.register(TestConsumer.class); this.context.refresh(); assertThat(this.context.getBeanNamesForType(TestProperties.class).length) .isEqualTo(0); assertThat(parent.getBeanNamesForType(TestProperties.class).length).isEqualTo(1); assertThat(this.context.getBean(TestConsumer.class).getName()).isEqualTo("foo"); } @Test public void testSimpleAutoConfig() throws Exception { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "external.name=foo"); this.context.register(ExampleConfig.class); this.context.refresh(); assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo"); } @Test public void testExplicitType() throws Exception { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "external.name=foo"); this.context.register(AnotherExampleConfig.class); this.context.refresh(); assertThat(this.context.containsBean("external-" + External.class.getName())) .isTrue(); assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo"); } @Test public void testMultipleExplicitTypes() throws Exception { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "external.name=foo", "another.name=bar"); this.context.register(FurtherExampleConfig.class); this.context.refresh(); assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo"); assertThat(this.context.getBean(Another.class).getName()).isEqualTo("bar"); } @Test public void testBindingWithMapKeyWithPeriod() { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "mymap.key1.key2:value12", "mymap.key3:value3"); this.context.register(ResourceBindingPropertiesWithMap.class); this.context.refresh(); ResourceBindingPropertiesWithMap bean = this.context .getBean(ResourceBindingPropertiesWithMap.class); assertThat(bean.mymap.get("key3")).isEqualTo("value3"); assertThat(bean.mymap.get("key1.key2")).isEqualTo("value12"); } @Test public void testAnnotatedBean() { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "external.name=bar", "spam.name=foo"); this.context.register(TestConfigurationWithAnnotatedBean.class); this.context.refresh(); assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo"); } /** * Strict tests need a known set of properties so we remove system items which may be * environment specific. */ private void removeSystemProperties() { MutablePropertySources sources = this.context.getEnvironment() .getPropertySources(); sources.remove("systemProperties"); sources.remove("systemEnvironment"); } @Configuration @EnableConfigurationProperties public static class TestConfigurationWithAnnotatedBean { @Bean @ConfigurationProperties(prefix = "spam") public External testProperties() { return new External(); } } @Configuration @EnableConfigurationProperties(TestProperties.class) protected static class TestConfiguration { } @Configuration @EnableConfigurationProperties(StrictTestProperties.class) protected static class StrictTestConfiguration { } @Configuration @EnableConfigurationProperties(EmbeddedTestProperties.class) protected static class EmbeddedTestConfiguration { } @Configuration @EnableConfigurationProperties(ExceptionIfInvalidTestProperties.class) protected static class ExceptionIfInvalidTestConfiguration { } @Configuration @EnableConfigurationProperties(IgnoredIfInvalidButNotValidatedTestProperties.class) protected static class IgnoredIfInvalidButNotValidatedTestConfiguration { } @Configuration @EnableConfigurationProperties(DerivedProperties.class) protected static class DerivedConfiguration { } @Configuration @EnableConfigurationProperties(NestedProperties.class) protected static class NestedConfiguration { } @Configuration protected static class DefaultConfiguration { @Bean public TestProperties testProperties() { TestProperties test = new TestProperties(); test.setName("bar"); return test; } } @Configuration @ImportResource("org/springframework/boot/context/properties/testProperties.xml") protected static class DefaultXmlConfiguration { } @EnableConfigurationProperties @Configuration public static class ExampleConfig { @Bean public External external() { return new External(); } } @EnableConfigurationProperties(External.class) @Configuration public static class AnotherExampleConfig { } @EnableConfigurationProperties({ External.class, Another.class }) @Configuration public static class FurtherExampleConfig { } @EnableConfigurationProperties({ SystemEnvVar.class }) @Configuration public static class SystemExampleConfig { } @ConfigurationProperties(prefix = "external") public static class External { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } @ConfigurationProperties(prefix = "another") public static class Another { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } @ConfigurationProperties(prefix = "spring_test_external") public static class SystemEnvVar { private String val; public String getVal() { return this.val; } public void setVal(String val) { this.val = val; } } @Component protected static class TestConsumer { @Autowired private TestProperties properties; @PostConstruct public void init() { assertThat(this.properties).isNotNull(); } public String getName() { return this.properties.name; } } @Configuration @EnableConfigurationProperties(MoreProperties.class) protected static class MoreConfiguration { } @Configuration @EnableConfigurationProperties(InvalidConfiguration.class) protected static class InvalidConfiguration { } @ConfigurationProperties protected static class NestedProperties { private String name; private final Nested nested = new Nested(); public void setName(String name) { this.name = name; } public Nested getNested() { return this.nested; } protected static class Nested { private String name; public void setName(String name) { this.name = name; } } } @ConfigurationProperties protected static class BaseProperties { private String name; public void setName(String name) { this.name = name; } } protected static class DerivedProperties extends BaseProperties { } @ConfigurationProperties protected static class TestProperties { private String name; private int[] array; private List<Integer> list = new ArrayList<>(); // No getter - you should be able to bind to a write-only bean public void setName(String name) { this.name = name; } public void setArray(int... values) { this.array = values; } public int[] getArray() { return this.array; } public List<Integer> getList() { return this.list; } public void setList(List<Integer> list) { this.list = list; } } @ConfigurationProperties(ignoreUnknownFields = false) protected static class StrictTestProperties extends TestProperties { } @ConfigurationProperties(prefix = "spring.foo") protected static class EmbeddedTestProperties extends TestProperties { } @ConfigurationProperties @Validated protected static class ExceptionIfInvalidTestProperties extends TestProperties { @NotNull private String description; public String getDescription() { return this.description; } public void setDescription(String description) { this.description = description; } } @ConfigurationProperties protected static class IgnoredIfInvalidButNotValidatedTestProperties extends TestProperties { @NotNull private String description; public String getDescription() { return this.description; } public void setDescription(String description) { this.description = description; } } @ConfigurationProperties protected static class MoreProperties { private String name; public void setName(String name) { this.name = name; } // No getter - you should be able to bind to a write-only bean } // No annotation protected static class InvalidProperties { private String name; public String getName() { return this.name; } public void setName(String name) { this.name = name; } } @EnableConfigurationProperties @ConfigurationProperties protected static class ResourceBindingPropertiesWithMap { private Map<String, String> mymap; public void setMymap(Map<String, String> mymap) { this.mymap = mymap; } public Map<String, String> getMymap() { return this.mymap; } } }