diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java index 83445c9ad16..7d58d9dd7cd 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -31,6 +31,9 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.support.ResourceEditorRegistrar; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.core.convert.ConversionService; @@ -52,7 +55,7 @@ import org.springframework.validation.Validator; * @author Dave Syer */ public class PropertiesConfigurationFactory - implements FactoryBean, MessageSourceAware, InitializingBean { + implements FactoryBean, ApplicationContextAware, MessageSourceAware, InitializingBean { private static final char[] EXACT_DELIMITERS = { '_', '.', '[' }; @@ -73,6 +76,8 @@ public class PropertiesConfigurationFactory private Validator validator; + private ApplicationContext applicationContext; + private MessageSource messageSource; private boolean hasBeenBound = false; @@ -149,6 +154,11 @@ public class PropertiesConfigurationFactory this.targetName = targetName; } + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + /** * Set the message source. * @param messageSource the message source @@ -265,6 +275,11 @@ public class PropertiesConfigurationFactory dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); customizeBinder(dataBinder); + if (this.applicationContext != null) { + ResourceEditorRegistrar resourceEditorRegistrar = new ResourceEditorRegistrar( + this.applicationContext, this.applicationContext.getEnvironment()); + resourceEditorRegistrar.registerCustomEditors(dataBinder); + } Iterable relaxedTargetNames = getRelaxedTargetNames(); Set names = getNames(relaxedTargetNames); PropertyValues propertyValues = getPropertySourcesPropertyValues(names, diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 62ee9b20db4..be56d2db7fc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -312,6 +312,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc PropertiesConfigurationFactory factory = new PropertiesConfigurationFactory( target); factory.setPropertySources(this.propertySources); + factory.setApplicationContext(this.applicationContext); factory.setValidator(determineValidator(bean)); // If no explicit conversion service is provided we add one so that (at least) // comma-separated arrays of convertibles can be bound automatically 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 5e81e4543fd..1119b5926b4 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -40,6 +40,10 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.ProtocolResolver; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.ReflectionTestUtils; @@ -52,6 +56,12 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link ConfigurationPropertiesBindingPostProcessor}. @@ -366,6 +376,38 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } } + @Test + public void customProtocolResolverIsInvoked() { + this.context = new AnnotationConfigApplicationContext(); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, + "test.resource=application.properties"); + ProtocolResolver protocolResolver = mock(ProtocolResolver.class); + given(protocolResolver.resolve(anyString(), any(ResourceLoader.class))) + .willReturn(null); + this.context.addProtocolResolver(protocolResolver); + this.context.register(PropertiesWithResource.class); + this.context.refresh(); + verify(protocolResolver).resolve(eq("application.properties"), + any(ResourceLoader.class)); + } + + @Test + public void customProtocolResolver() { + this.context = new AnnotationConfigApplicationContext(); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, + "test.resource=test:/application.properties"); + this.context.addProtocolResolver(new TestProtocolResolver()); + this.context.register(PropertiesWithResource.class); + this.context.refresh(); + Resource resource = this.context.getBean(PropertiesWithResource.class) + .getResource(); + assertThat(resource).isNotNull(); + assertThat(resource).isInstanceOf(ClassPathResource.class); + assertThat(resource.exists()).isTrue(); + assertThat(((ClassPathResource) resource).getPath()) + .isEqualTo("application.properties"); + } + @Configuration @EnableConfigurationProperties public static class TestConfigurationWithValidatingSetter { @@ -819,4 +861,35 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + @EnableConfigurationProperties + @ConfigurationProperties(prefix = "test") + public static class PropertiesWithResource { + + private Resource resource; + + public Resource getResource() { + return this.resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + } + + private static class TestProtocolResolver implements ProtocolResolver { + + public static final String PREFIX = "test:/"; + + @Override + public Resource resolve(String location, ResourceLoader resourceLoader) { + if (location.startsWith(PREFIX)) { + String path = location.substring(PREFIX.length(), location.length()); + return new ClassPathResource(path); + } + return null; + } + } + }