diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index a60e901920..dc0249e060 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.PostConstruct; import javax.validation.constraints.NotNull; @@ -46,14 +47,19 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.SystemEnvironmentPropertySource; +import org.springframework.lang.Nullable; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; @@ -61,6 +67,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.fail; /** @@ -472,6 +479,51 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertThat(duration.getSeconds()).isEqualTo(60); } + @Test + public void converterIsFound() { + prepareConverterContext(ConverterConfiguration.class, PersonProperty.class); + this.context.refresh(); + Person person = this.context.getBean(PersonProperty.class).getPerson(); + assertThat(person.firstName).isEqualTo("John"); + assertThat(person.lastName).isEqualTo("Smith"); + } + + @Test + public void converterWithoutQualifierIsNotInvoked() { + prepareConverterContext(NonQualifiedConverterConfiguration.class, + PersonProperty.class); + this.thrown.expect(BeanCreationException.class); + this.thrown.expectCause(instanceOf(BindException.class)); + this.context.refresh(); + } + + @Test + public void genericConverterIsFound() { + prepareConverterContext(GenericConverterConfiguration.class, PersonProperty.class); + this.context.refresh(); + Person person = this.context.getBean(PersonProperty.class).getPerson(); + assertThat(person.firstName).isEqualTo("John"); + assertThat(person.lastName).isEqualTo("Smith"); + } + + @Test + public void genericConverterWithoutQualifierIsNotInvoked() { + prepareConverterContext(NonQualifiedGenericConverterConfiguration.class, + PersonProperty.class); + this.thrown.expect(BeanCreationException.class); + this.thrown.expectCause(instanceOf(BindException.class)); + this.context.refresh(); + } + + private void prepareConverterContext(Class... config) { + this.context = new AnnotationConfigApplicationContext(); + MutablePropertySources sources = this.context.getEnvironment() + .getPropertySources(); + sources.addFirst(new MapPropertySource("test", + Collections.singletonMap("test.person", "John Smith"))); + this.context.register(config); + } + private void assertBindingFailure(int errorCount) { try { this.context.refresh(); @@ -1037,4 +1089,103 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + static class ConverterConfiguration { + + @Bean + @ConfigurationPropertiesBinding + public Converter personConverter() { + return new PersonConverter(); + } + + } + + @Configuration + static class NonQualifiedConverterConfiguration { + + @Bean + public Converter personConverter() { + return new PersonConverter(); + } + + } + + private static class PersonConverter implements Converter { + + @Nullable + @Override + public Person convert(String source) { + String[] content = StringUtils.split(source, " "); + return new Person(content[0], content[1]); + } + } + + @Configuration + static class GenericConverterConfiguration { + + @Bean + @ConfigurationPropertiesBinding + public GenericConverter genericPersonConverter() { + return new GenericPersonConverter(); + } + + } + + @Configuration + static class NonQualifiedGenericConverterConfiguration { + + @Bean + public GenericConverter genericPersonConverter() { + return new GenericPersonConverter(); + } + + } + + private static class GenericPersonConverter implements GenericConverter { + + @Nullable + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Person.class)); + } + + @Nullable + @Override + public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + String[] content = StringUtils.split((String) source, " "); + return new Person(content[0], content[1]); + } + } + + + @Configuration + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + public static class PersonProperty { + + private Person person; + + public Person getPerson() { + return this.person; + } + + public void setPerson(Person person) { + this.person = person; + } + + } + + static class Person { + + private final String firstName; + + private final String lastName; + + Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + } + }