diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java new file mode 100644 index 0000000000..3f60e74e83 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -0,0 +1,202 @@ +/* + * 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 org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; +import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler; +import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; +import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; +import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +/** + * Bind {@link ConfigurationProperties} annotated object from a configurable list of + * {@link PropertySource}. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @see ConfigurationPropertiesBinderBuilder + */ +public class ConfigurationPropertiesBinder { + + private final Iterable> propertySources; + + private final ConversionService conversionService; + + private final Validator validator; + + private Iterable configurationSources; + + ConfigurationPropertiesBinder(Iterable> propertySources, + ConversionService conversionService, Validator validator) { + Assert.notNull(propertySources, "PropertySources must not be null"); + this.propertySources = propertySources; + this.conversionService = conversionService; + this.validator = validator; + if (propertySources instanceof MutablePropertySources) { + this.configurationSources = ConfigurationPropertySources + .from((MutablePropertySources) propertySources); + } + else { + this.configurationSources = ConfigurationPropertySources + .from(propertySources); + } + } + + /** + * Bind the specified {@code target} object if it is annotated with + * {@link ConfigurationProperties}, otherwise ignore it. + * @param target the target to bind the configuration property sources to + * @throws ConfigurationPropertiesBindingException if the binding failed + */ + public void bind(Object target) { + ConfigurationProperties annotation = AnnotationUtils.findAnnotation( + target.getClass(), ConfigurationProperties.class); + if (annotation != null) { + bind(target, annotation); + } + } + + /** + * Bind the specified {@code target} object using the configuration defined by + * the specified {@code annotation}. + * @param target the target to bind the configuration property sources to + * @param annotation the binding configuration + * @throws ConfigurationPropertiesBindingException if the binding failed + */ + void bind(Object target, ConfigurationProperties annotation) { + Binder binder = new Binder(this.configurationSources, + new PropertySourcesPlaceholdersResolver(this.propertySources), + this.conversionService); + Validator validator = determineValidator(target); + BindHandler handler = getBindHandler(annotation, validator); + Bindable bindable = Bindable.ofInstance(target); + try { + binder.bind(annotation.prefix(), bindable, handler); + } + catch (Exception ex) { + throw new ConfigurationPropertiesBindingException(target.getClass(), + getAnnotationDetails(annotation), ex); + } + } + + /** + * Destroy this binder instance. + */ + void destroy() { + if (this.validator instanceof InternalValidator) { + ((InternalValidator) this.validator).destroy(); + } + } + + private Validator determineValidator(Object bean) { + boolean supportsBean = (this.validator != null + && this.validator.supports(bean.getClass())); + if (ClassUtils.isAssignable(Validator.class, bean.getClass())) { + if (supportsBean) { + return new ChainingValidator(this.validator, (Validator) bean); + } + return (Validator) bean; + } + return (supportsBean ? this.validator : null); + } + + private BindHandler getBindHandler(ConfigurationProperties annotation, + Validator validator) { + BindHandler handler = BindHandler.DEFAULT; + if (annotation.ignoreInvalidFields()) { + handler = new IgnoreErrorsBindHandler(handler); + } + if (!annotation.ignoreUnknownFields()) { + UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter(); + handler = new NoUnboundElementsBindHandler(handler, filter); + } + if (validator != null) { + handler = new ValidationBindHandler(handler, validator); + } + return handler; + } + + private String getAnnotationDetails(ConfigurationProperties annotation) { + if (annotation == null) { + return ""; + } + StringBuilder details = new StringBuilder(); + details.append("prefix=").append(annotation.prefix()); + details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields()); + details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields()); + return details.toString(); + } + + + /** + * {@link Validator} extension to be implemented to signal that that validator can + * be destroyed once the binder is no longer in use. + */ + interface InternalValidator extends Validator { + + void destroy(); + + } + + /** + * {@link Validator} implementation that wraps {@link Validator} instances and chains + * their execution. + */ + private static class ChainingValidator implements Validator { + + private final Validator[] validators; + + ChainingValidator(Validator... validators) { + Assert.notNull(validators, "Validators must not be null"); + this.validators = validators; + } + + @Override + public boolean supports(Class clazz) { + for (Validator validator : this.validators) { + if (validator.supports(clazz)) { + return true; + } + } + return false; + } + + @Override + public void validate(Object target, Errors errors) { + for (Validator validator : this.validators) { + if (validator.supports(target.getClass())) { + validator.validate(target, errors); + } + } + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilder.java new file mode 100644 index 0000000000..9e4dc1a2ad --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilder.java @@ -0,0 +1,227 @@ +/* + * 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.Collections; +import java.util.List; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.util.ClassUtils; +import org.springframework.validation.Validator; + +/** + * Builder for creating {@link ConfigurationPropertiesBinder} based on the state of + * the {@link ApplicationContext}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class ConfigurationPropertiesBinderBuilder { + + /** + * The bean name of the configuration properties validator. + */ + public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; + + private static final String CONVERSION_SERVICE_BEAN_NAME = ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME; + + private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator", + "javax.validation.ValidatorFactory" }; + + private final ApplicationContext applicationContext; + + private ConversionService conversionService; + + private Validator validator; + + private Iterable> propertySources; + + /** + * Creates an instance with the {@link ApplicationContext} to use. + * @param applicationContext the application context + */ + public ConfigurationPropertiesBinderBuilder(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Specify the {@link ConversionService} to use or {@code null} to use the default. + *

+ * By default, use a {@link ConversionService} bean named + * {@value #CONVERSION_SERVICE_BEAN_NAME} if any. Otherwise create a + * {@link DefaultConversionService} with any {@link ConfigurationPropertiesBinding} + * qualified {@link Converter} and {@link GenericConverter} beans found in the + * context. + * @param conversionService the conversion service to use or {@code null} + * @return this instance + */ + public ConfigurationPropertiesBinderBuilder withConversionService( + ConversionService conversionService) { + this.conversionService = conversionService; + return this; + } + + /** + * Specify the {@link Validator} to use or {@code null} to use the default. + *

+ * By default, use a {@link Validator} bean named {@value VALIDATOR_BEAN_NAME} if + * any. If not, create a JSR 303 Validator if the necessary libraries are available. + * No validation occurs otherwise. + * @param validator the validator to use or {@code null} + * @return this instance + */ + public ConfigurationPropertiesBinderBuilder withValidator(Validator validator) { + this.validator = validator; + return this; + } + + /** + * Specify the {@link PropertySource property sources} to use. + * @param propertySources the configuration the binder should use + * @return this instance + * @see #withEnvironment(ConfigurableEnvironment) + */ + public ConfigurationPropertiesBinderBuilder withPropertySources( + Iterable> propertySources) { + this.propertySources = propertySources; + return this; + } + + /** + * Specify the {@link ConfigurableEnvironment Environment} to use, use all + * available {@link PropertySource}. + * @param environment the environment to use + * @return this instance + * @see #withPropertySources(Iterable) + */ + public ConfigurationPropertiesBinderBuilder withEnvironment( + ConfigurableEnvironment environment) { + return withPropertySources(environment.getPropertySources()); + } + + /** + * Build a {@link ConfigurationPropertiesBinder} based on the state of the builder, + * discovering the {@lik ConversionService} and {@link Validator} if necessary. + * @return a {@link ConfigurationPropertiesBinder} + */ + public ConfigurationPropertiesBinder build() { + return new ConfigurationPropertiesBinder(this.propertySources, + determineConversionService(), determineValidator()); + } + + private Validator determineValidator() { + if (this.validator != null) { + return this.validator; + } + Validator defaultValidator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class); + if (defaultValidator != null) { + return defaultValidator; + } + if (isJsr303Present()) { + return new ValidatedLocalValidatorFactoryBean(this.applicationContext); + } + return null; + } + + private ConversionService determineConversionService() { + if (this.conversionService != null) { + return this.conversionService; + } + ConversionService conversionServiceByName = getOptionalBean( + CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); + if (conversionServiceByName != null) { + return conversionServiceByName; + } + return createDefaultConversionService(); + } + + private ConversionService createDefaultConversionService() { + ConversionServiceFactory conversionServiceFactory = this.applicationContext + .getAutowireCapableBeanFactory().createBean(ConversionServiceFactory.class); + return conversionServiceFactory.createConversionService(); + } + + private boolean isJsr303Present() { + for (String validatorClass : VALIDATOR_CLASSES) { + if (!ClassUtils.isPresent(validatorClass, + this.applicationContext.getClassLoader())) { + return false; + } + } + return true; + } + + private T getOptionalBean(String name, Class type) { + try { + return this.applicationContext.getBean(name, type); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + + private static class ConversionServiceFactory { + + private List> converters = Collections.emptyList(); + + private List genericConverters = Collections.emptyList(); + + /** + * A list of custom converters (in addition to the defaults) to use when + * converting properties for binding. + * @param converters the converters to set + */ + @Autowired(required = false) + @ConfigurationPropertiesBinding + public void setConverters(List> converters) { + this.converters = converters; + } + + /** + * A list of custom converters (in addition to the defaults) to use when + * converting properties for binding. + * @param converters the converters to set + */ + @Autowired(required = false) + @ConfigurationPropertiesBinding + public void setGenericConverters(List converters) { + this.genericConverters = converters; + } + + public ConversionService createConversionService() { + DefaultConversionService conversionService = new DefaultConversionService(); + for (Converter converter : this.converters) { + conversionService.addConverter(converter); + } + for (GenericConverter genericConverter : this.genericConverters) { + conversionService.addConverter(genericConverter); + } + return conversionService; + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingException.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingException.java new file mode 100644 index 0000000000..49f2f86cc7 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingException.java @@ -0,0 +1,56 @@ +/* + * 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 org.springframework.core.NestedExceptionUtils; +import org.springframework.util.ClassUtils; + +/** + * Exception thrown when a {@code @ConfigurationProperties} annotated object failed + * to be bound. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class ConfigurationPropertiesBindingException extends RuntimeException { + + private final Class targetClass; + + public ConfigurationPropertiesBindingException(Class targetClass, + String message, Throwable cause) { + super("Could not bind properties to '" + ClassUtils.getShortName(targetClass) + + "': " + message, cause); + this.targetClass = targetClass; + } + + /** + * Return the target type of the object that failed to be bound. + * @return the target {@link Class} + */ + public Class getTargetClass() { + return this.targetClass; + } + + /** + * Retrieve the innermost cause of this exception, if any. + * @return the innermost exception, or {@code null} if none + */ + public Throwable getRootCause() { + return NestedExceptionUtils.getRootCause(this); + } + +} 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 7a1356b9ad..b01fbf7135 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 @@ -16,8 +16,6 @@ package org.springframework.boot.context.properties; -import java.util.Collections; -import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -30,48 +28,23 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.boot.context.properties.bind.BindHandler; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; -import org.springframework.boot.context.properties.bind.convert.BinderConversionService; -import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler; -import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; -import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler; -import org.springframework.boot.context.properties.source.ConfigurationPropertySource; -import org.springframework.boot.context.properties.source.ConfigurationPropertySources; -import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter; -import org.springframework.boot.validation.MessageInterpolatorFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.StandardEnvironment; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import org.springframework.validation.annotation.Validated; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; /** * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with @@ -87,14 +60,6 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean, DisposableBean, ApplicationListener, PriorityOrdered { - /** - * The bean name of the configuration properties validator. - */ - public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; - - private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator", - "javax.validation.ValidatorFactory" }; - private static final Log logger = LogFactory .getLog(ConfigurationPropertiesBindingPostProcessor.class); @@ -104,49 +69,17 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc private Validator validator; - private volatile Validator localValidator; - private ConversionService conversionService; - private DefaultConversionService defaultConversionService; - private BeanFactory beanFactory; private Environment environment = new StandardEnvironment(); private ApplicationContext applicationContext; - private List> converters = Collections.emptyList(); - - private List genericConverters = Collections.emptyList(); - private int order = Ordered.HIGHEST_PRECEDENCE + 1; - private Iterable configurationSources; - - private BinderConversionService binderConversionService; - - /** - * A list of custom converters (in addition to the defaults) to use when converting - * properties for binding. - * @param converters the converters to set - */ - @Autowired(required = false) - @ConfigurationPropertiesBinding - public void setConverters(List> converters) { - this.converters = converters; - } - - /** - * A list of custom converters (in addition to the defaults) to use when converting - * properties for binding. - * @param converters the converters to set - */ - @Autowired(required = false) - @ConfigurationPropertiesBinding - public void setGenericConverters(List converters) { - this.genericConverters = converters; - } + private ConfigurationPropertiesBinder configurationPropertiesBinder; /** * Set the order of the bean. @@ -217,45 +150,23 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc if (this.propertySources == null) { this.propertySources = deducePropertySources(); } - if (this.validator == null) { - this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class); - } - if (this.conversionService == null) { - this.conversionService = getOptionalBean( - ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, - ConversionService.class); - } - if (this.propertySources instanceof MutablePropertySources) { - this.configurationSources = ConfigurationPropertySources - .from((MutablePropertySources) this.propertySources); - } - else { - this.configurationSources = ConfigurationPropertySources - .from(this.propertySources); - } } @Override public void onApplicationEvent(ContextRefreshedEvent event) { - freeLocalValidator(); + freeBinder(); } @Override - public void destroy() throws Exception { - freeLocalValidator(); + public void destroy() { + freeBinder(); } - private void freeLocalValidator() { - try { - Validator validator = this.localValidator; - this.localValidator = null; - if (validator != null) { - ((DisposableBean) validator).destroy(); - } - } - catch (Exception ex) { - throw new IllegalStateException(ex); + private void freeBinder() { + if (this.configurationPropertiesBinder != null) { + this.configurationPropertiesBinder.destroy(); } + this.configurationPropertiesBinder = null; } private PropertySources deducePropertySources() { @@ -289,25 +200,27 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc return null; } - private T getOptionalBean(String name, Class type) { - try { - return this.beanFactory.getBean(name, type); - } - catch (NoSuchBeanDefinitionException ex) { - return null; - } - } - @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigurationProperties annotation = getAnnotation(bean, beanName); if (annotation != null) { - postProcessBeforeInitialization(bean, beanName, annotation); + try { + getBinder().bind(bean, annotation); + } + catch (ConfigurationPropertiesBindingException ex) { + throw new BeanCreationException(beanName, ex.getMessage(), ex.getCause()); + } } return bean; } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + private ConfigurationProperties getAnnotation(Object bean, String beanName) { ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class); @@ -318,188 +231,14 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc return annotation; } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - private void postProcessBeforeInitialization(Object bean, String beanName, - ConfigurationProperties annotation) { - Binder binder = new Binder(this.configurationSources, - new PropertySourcesPlaceholdersResolver(this.propertySources), - getBinderConversionService()); - Validator validator = determineValidator(bean); - BindHandler handler = getBindHandler(annotation, validator); - Bindable bindable = Bindable.ofInstance(bean); - try { - binder.bind(annotation.prefix(), bindable, handler); + private ConfigurationPropertiesBinder getBinder() { + if (this.configurationPropertiesBinder == null) { + this.configurationPropertiesBinder = new ConfigurationPropertiesBinderBuilder(this.applicationContext) + .withConversionService(this.conversionService) + .withValidator(this.validator) + .withPropertySources(this.propertySources).build(); } - catch (Exception ex) { - String targetClass = ClassUtils.getShortName(bean.getClass()); - throw new BeanCreationException(beanName, "Could not bind properties to " - + targetClass + " (" + getAnnotationDetails(annotation) + ")", ex); - } - } - - private BinderConversionService getBinderConversionService() { - BinderConversionService binderConversionService = this.binderConversionService; - if (binderConversionService == null) { - ConversionService conversionService = this.conversionService; - if (conversionService == null) { - conversionService = getDefaultConversionService(); - } - binderConversionService = new BinderConversionService(conversionService); - this.binderConversionService = binderConversionService; - } - return binderConversionService; - } - - private ConversionService getDefaultConversionService() { - if (this.defaultConversionService == null) { - DefaultConversionService conversionService = new DefaultConversionService(); - this.applicationContext.getAutowireCapableBeanFactory().autowireBean(this); - for (Converter converter : this.converters) { - conversionService.addConverter(converter); - } - for (GenericConverter genericConverter : this.genericConverters) { - conversionService.addConverter(genericConverter); - } - this.defaultConversionService = conversionService; - } - return this.defaultConversionService; - } - - private String getAnnotationDetails(ConfigurationProperties annotation) { - if (annotation == null) { - return ""; - } - StringBuilder details = new StringBuilder(); - details.append("prefix=").append(annotation.prefix()); - details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields()); - details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields()); - return details.toString(); - } - - private Validator determineValidator(Object bean) { - Validator validator = getValidator(); - boolean supportsBean = (validator != null && validator.supports(bean.getClass())); - if (ClassUtils.isAssignable(Validator.class, bean.getClass())) { - if (supportsBean) { - return new ChainingValidator(validator, (Validator) bean); - } - return (Validator) bean; - } - return (supportsBean ? validator : null); - } - - private Validator getValidator() { - if (this.validator != null) { - return this.validator; - } - if (this.localValidator == null && isJsr303Present()) { - this.localValidator = new ValidatedLocalValidatorFactoryBean( - this.applicationContext); - } - return this.localValidator; - } - - private boolean isJsr303Present() { - for (String validatorClass : VALIDATOR_CLASSES) { - if (!ClassUtils.isPresent(validatorClass, - this.applicationContext.getClassLoader())) { - return false; - } - } - return true; - } - - private BindHandler getBindHandler(ConfigurationProperties annotation, - Validator validator) { - BindHandler handler = BindHandler.DEFAULT; - if (annotation.ignoreInvalidFields()) { - handler = new IgnoreErrorsBindHandler(handler); - } - if (!annotation.ignoreUnknownFields()) { - UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter(); - handler = new NoUnboundElementsBindHandler(handler, filter); - } - if (validator != null) { - handler = new ValidationBindHandler(handler, validator); - } - return handler; - } - - /** - * {@link LocalValidatorFactoryBean} supports classes annotated with - * {@link Validated @Validated}. - */ - private static class ValidatedLocalValidatorFactoryBean - extends LocalValidatorFactoryBean { - - private static final Log logger = LogFactory - .getLog(ConfigurationPropertiesBindingPostProcessor.class); - - ValidatedLocalValidatorFactoryBean(ApplicationContext applicationContext) { - setApplicationContext(applicationContext); - setMessageInterpolator(new MessageInterpolatorFactory().getObject()); - afterPropertiesSet(); - } - - @Override - public boolean supports(Class type) { - if (!super.supports(type)) { - return false; - } - if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) { - return true; - } - if (type.getPackage() != null && type.getPackage().getName() - .startsWith("org.springframework.boot")) { - return false; - } - if (getConstraintsForClass(type).isBeanConstrained()) { - logger.warn("The @ConfigurationProperties bean " + type - + " contains validation constraints but had not been annotated " - + "with @Validated."); - } - return true; - } - - } - - /** - * {@link Validator} implementation that wraps {@link Validator} instances and chains - * their execution. - */ - private static class ChainingValidator implements Validator { - - private final Validator[] validators; - - ChainingValidator(Validator... validators) { - Assert.notNull(validators, "Validators must not be null"); - this.validators = validators; - } - - @Override - public boolean supports(Class clazz) { - for (Validator validator : this.validators) { - if (validator.supports(clazz)) { - return true; - } - } - return false; - } - - @Override - public void validate(Object target, Errors errors) { - for (Validator validator : this.validators) { - if (validator.supports(target.getClass())) { - validator.validate(target, errors); - } - } - } - + return this.configurationPropertiesBinder; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ValidatedLocalValidatorFactoryBean.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ValidatedLocalValidatorFactoryBean.java new file mode 100644 index 0000000000..5628740d65 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ValidatedLocalValidatorFactoryBean.java @@ -0,0 +1,66 @@ +/* + * 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.validation.MessageInterpolatorFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +/** + * {@link LocalValidatorFactoryBean} supports classes annotated with + * {@link Validated @Validated}. + * + * @author Phillip Webb + */ +class ValidatedLocalValidatorFactoryBean extends LocalValidatorFactoryBean + implements ConfigurationPropertiesBinder.InternalValidator { + + private static final Log logger = LogFactory + .getLog(ConfigurationPropertiesBindingPostProcessor.class); + + ValidatedLocalValidatorFactoryBean(ApplicationContext applicationContext) { + setApplicationContext(applicationContext); + setMessageInterpolator(new MessageInterpolatorFactory().getObject()); + afterPropertiesSet(); + } + + @Override + public boolean supports(Class type) { + if (!super.supports(type)) { + return false; + } + if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) { + return true; + } + if (type.getPackage() != null && type.getPackage().getName() + .startsWith("org.springframework.boot")) { + return false; + } + if (getConstraintsForClass(type).isBeanConstrained()) { + logger.warn("The @ConfigurationProperties bean " + type + + " contains validation constraints but had not been annotated " + + "with @Validated."); + } + return true; + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java new file mode 100644 index 0000000000..061dbc689d --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java @@ -0,0 +1,263 @@ +/* + * 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.time.Duration; + +import javax.validation.constraints.NotNull; + +import org.junit.Test; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.context.properties.bind.validation.BindValidationException; +import org.springframework.boot.context.properties.bind.validation.ValidationErrors; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.DefaultConversionService; +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; +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link ConfigurationPropertiesBinderBuilder}. + * + * @author Stephane Nicoll + */ +public class ConfigurationPropertiesBinderBuilderTests { + + private final StaticApplicationContext applicationContext = new StaticApplicationContext(); + + private final ConfigurationPropertiesBinderBuilder builder = + new ConfigurationPropertiesBinderBuilder(this.applicationContext); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + public void useCustomConversionService() { + DefaultConversionService conversionService = new DefaultConversionService(); + conversionService.addConverter(new AddressConverter()); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.address=FooStreet 42"); + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment) + .withConversionService(conversionService).build(); + PropertyWithAddress target = new PropertyWithAddress(); + binder.bind(target); + assertThat(target.getAddress()).isNotNull(); + assertThat(target.getAddress().streetName).isEqualTo("FooStreet"); + assertThat(target.getAddress().number).isEqualTo(42); + } + + @Test + public void detectDefaultConversionService() { + this.applicationContext.registerSingleton("conversionService", + DefaultConversionService.class); + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + assertThat(ReflectionTestUtils.getField(binder, "conversionService")).isSameAs( + this.applicationContext.getBean("conversionService")); + } + + @Test + public void bindToJavaTimeDuration() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.duration=PT1M"); + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + PropertyWithDuration target = new PropertyWithDuration(); + binder.bind(target); + assertThat(target.getDuration().getSeconds()).isEqualTo(60); + } + + @Test + public void useCustomValidator() { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment) + .withValidator(validator).build(); + assertThat(ReflectionTestUtils.getField(binder, "validator")).isSameAs(validator); + } + + @Test + public void detectDefaultValidator() { + this.applicationContext.registerSingleton("configurationPropertiesValidator", + LocalValidatorFactoryBean.class); + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + assertThat(ReflectionTestUtils.getField(binder, "validator")).isSameAs( + this.applicationContext.getBean("configurationPropertiesValidator")); + } + + @Test + public void validationWithoutJsr303() { + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + assertThat(bindWithValidationErrors(binder, new PropertyWithoutJSR303()) + .getAllErrors()).hasSize(1); + } + + @Test + public void validationWithJsr303() { + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + assertThat(bindWithValidationErrors(binder, new PropertyWithJSR303()) + .getAllErrors()).hasSize(2); + } + + @Test + public void validationWithJsr303AndValidInput() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.foo=123456", "test.bar=654321"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithJSR303 target = new PropertyWithJSR303(); + binder.bind(target); + assertThat(target.getFoo()).isEqualTo("123456"); + assertThat(target.getBar()).isEqualTo("654321"); + } + + @Test + public void internalValidatorIsClosed() throws Exception { + ConfigurationPropertiesBinder binder = this.builder + .withEnvironment(this.environment).build(); + Object validator = ReflectionTestUtils.getField(binder, "validator"); + assertThat(validator).isNotNull(); + assertThat(validator).isInstanceOf(DisposableBean.class); + DisposableBean validatorSpy = spy((DisposableBean) validator); + ReflectionTestUtils.setField(binder, "validator", validatorSpy); + binder.destroy(); + verify(validatorSpy).destroy(); + } + + private ValidationErrors bindWithValidationErrors( + ConfigurationPropertiesBinder binder, Object target) { + try { + binder.bind(target); + throw new AssertionError("Should have failed to bind " + target); + } + catch (ConfigurationPropertiesBindingException ex) { + Throwable rootCause = ex.getRootCause(); + assertThat(rootCause).isInstanceOf(BindValidationException.class); + return ((BindValidationException) rootCause).getValidationErrors(); + } + } + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithAddress { + + private Address address; + + public Address getAddress() { + return this.address; + } + + public void setAddress(Address address) { + this.address = address; + } + + } + + private static class Address { + + private String streetName; + + private Integer number; + + Address(String streetName, Integer number) { + this.streetName = streetName; + this.number = number; + } + + } + + private static class AddressConverter implements Converter { + @Override + public Address convert(String source) { + String[] split = StringUtils.split(source, " "); + return new Address(split[0], Integer.valueOf(split[1])); + } + } + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithDuration { + + private Duration duration; + + public Duration getDuration() { + return this.duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + } + + @ConfigurationProperties(prefix = "test") + @Validated + public static class PropertyWithoutJSR303 implements Validator { + + private String foo; + + @Override + public boolean supports(Class clazz) { + return clazz.isAssignableFrom(getClass()); + } + + @Override + public void validate(Object target, Errors errors) { + ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1"); + } + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + + @ConfigurationProperties(prefix = "test") + @Validated + public static class PropertyWithJSR303 extends PropertyWithoutJSR303 { + + @NotNull + private String bar; + + public void setBar(String bar) { + this.bar = bar; + } + + public String getBar() { + return this.bar; + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java new file mode 100644 index 0000000000..6e663c2375 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java @@ -0,0 +1,483 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.context.properties.bind.BindException; +import org.springframework.boot.context.properties.bind.validation.BindValidationException; +import org.springframework.boot.context.properties.bind.validation.ValidationErrors; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.SystemEnvironmentPropertySource; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; +import org.springframework.validation.annotation.Validated; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link ConfigurationPropertiesBinder}. + * + * @author Stephane Nicoll + */ +public class ConfigurationPropertiesBinderTests { + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + public void bindSimpleProperties() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "person.name=John Smith", "person.age=42"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PersonProperties target = new PersonProperties(); + binder.bind(target); + assertThat(target.name).isEqualTo("John Smith"); + assertThat(target.age).isEqualTo(42); + } + + @Test + public void bindUnknownFieldFailureMessageContainsDetailsOfPropertyOrigin() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "person.does-not-exist=yolo"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PersonProperties target = new PersonProperties(); + try { + binder.bind(target); + fail("Expected exception"); + } + catch (ConfigurationPropertiesBindingException ex) { + BindException bindException = (BindException) ex.getCause(); + assertThat(bindException.getMessage()) + .startsWith("Failed to bind properties under 'person' to " + + PersonProperties.class.getName()); + } + } + + @Test + public void bindWithIgnoreInvalidFieldsAnnotation() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "com.example.bar=spam"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithIgnoreInvalidFields target = new PropertyWithIgnoreInvalidFields(); + binder.bind(target); + assertThat(target.getBar()).isEqualTo(0); + } + + @Test + public void bindNonAnnotatedObject() { + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + binder.bind("FooBar"); + } + + @Test + public void bindToEnum() { + bindToEnum("test.theValue=foo"); + } + + @Test + public void bindToEnumRelaxed() { + bindToEnum("test.the-value=FoO"); + bindToEnum("test.THE_VALUE=FoO"); + } + + private void bindToEnum(String property) { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, property); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithEnum target = new PropertyWithEnum(); + binder.bind(target); + assertThat(target.getTheValue()).isEqualTo(FooEnum.FOO); + } + + @Test + public void bindSetOfEnumRelaxed() { + bindToEnumSet("test.the-values=foo,bar", FooEnum.FOO, FooEnum.BAR); + bindToEnumSet("test.the-values=foo", FooEnum.FOO); + } + + private void bindToEnumSet(String property, FooEnum... expected) { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, property); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithEnum target = new PropertyWithEnum(); + binder.bind(target); + assertThat(target.getTheValues()).contains(expected); + } + + @Test + public void bindToCharArray() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.chars=word"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithCharArray target = new PropertyWithCharArray(); + binder.bind(target); + assertThat(target.getChars()).isEqualTo("word".toCharArray()); + } + + @Test + public void bindToRelaxedPropertyNamesSame() throws Exception { + testRelaxedPropertyNames("test.FOO_BAR=test1", "test.FOO_BAR=test2", + "test.BAR-B-A-Z=testa", "test.BAR-B-A-Z=testb"); + } + + private void testRelaxedPropertyNames(String... pairs) { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + pairs); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithRelaxedNames target = new PropertyWithRelaxedNames(); + binder.bind(target); + assertThat(target.getFooBar()).isEqualTo("test2"); + assertThat(target.getBarBAZ()).isEqualTo("testb"); + } + + @Test + public void bindToNestedProperty() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.nested.value=test1"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithNestedValue target = new PropertyWithNestedValue(); + binder.bind(target); + assertThat(target.getNested().getValue()).isEqualTo("test1"); + } + + @Test + public void bindToMap() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.map.foo=bar"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertiesWithMap target = new PropertiesWithMap(); + binder.bind(target); + assertThat(target.getMap()).containsOnly(entry("foo", "bar")); + } + + @Test + public void bindToMapWithSystemProperties() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addLast(new SystemEnvironmentPropertySource("system", + Collections.singletonMap("TEST_MAP_FOO_BAR", "baz"))); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + propertySources, null, null); + PropertiesWithComplexMap target = new PropertiesWithComplexMap(); + binder.bind(target); + assertThat(target.getMap()).containsOnlyKeys("foo"); + assertThat(target.getMap().get("foo")).containsOnly(entry("bar", "baz")); + } + + @Test + public void bindWithOverriddenProperties() { + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new SystemEnvironmentPropertySource("system", + Collections.singletonMap("PERSON_NAME", "Jane"))); + propertySources.addLast(new MapPropertySource("test", + Collections.singletonMap("person.name", "John"))); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + propertySources, null, null); + PersonProperties target = new PersonProperties(); + binder.bind(target); + assertThat(target.name).isEqualTo("Jane"); + } + + @Test + public void validationWithSetter() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.foo=spam"); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, null); + PropertyWithValidatingSetter target = new PropertyWithValidatingSetter(); + try { + binder.bind(target); + fail("Expected exception"); + } + catch (ConfigurationPropertiesBindingException ex) { + BindException bindException = (BindException) ex.getCause(); + assertThat(bindException.getMessage()) + .startsWith("Failed to bind properties under 'test' to " + + PropertyWithValidatingSetter.class.getName()); + } + } + + @Test + public void validationWithCustomValidator() { + CustomPropertyValidator validator = spy(new CustomPropertyValidator()); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, + validator); + PropertyWithCustomValidator target = new PropertyWithCustomValidator(); + assertThat(bindWithValidationErrors(binder, target) + .getAllErrors()).hasSize(1); + verify(validator).validate(eq(target), any(Errors.class)); + } + + @Test + public void validationWithCustomValidatorNotSupported() { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "test.foo=bar"); + CustomPropertyValidator validator = spy(new CustomPropertyValidator()); + ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder( + this.environment.getPropertySources(), null, + validator); + PropertyWithValidatingSetter target = new PropertyWithValidatingSetter(); + binder.bind(target); + assertThat(target.getFoo()).isEqualTo("bar"); + verify(validator, times(0)).validate(eq(target), any(Errors.class)); + } + + private ValidationErrors bindWithValidationErrors( + ConfigurationPropertiesBinder binder, Object target) { + try { + binder.bind(target); + throw new AssertionError("Should have failed to bind " + target); + } + catch (ConfigurationPropertiesBindingException ex) { + Throwable rootCause = ex.getRootCause(); + assertThat(rootCause).isInstanceOf(BindValidationException.class); + return ((BindValidationException) rootCause).getValidationErrors(); + } + } + + @ConfigurationProperties(value = "person", ignoreUnknownFields = false) + static class PersonProperties { + + private String name; + + private Integer age; + + public void setName(String name) { + this.name = name; + } + + public void setAge(Integer age) { + this.age = age; + } + + } + + @ConfigurationProperties(prefix = "com.example", ignoreInvalidFields = true) + public static class PropertyWithIgnoreInvalidFields { + + private long bar; + + public void setBar(long bar) { + this.bar = bar; + } + + public long getBar() { + return this.bar; + } + + } + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithEnum { + + private FooEnum theValue; + + private List theValues; + + public void setTheValue(FooEnum value) { + this.theValue = value; + } + + public FooEnum getTheValue() { + return this.theValue; + } + + public List getTheValues() { + return this.theValues; + } + + public void setTheValues(List theValues) { + this.theValues = theValues; + } + + } + + enum FooEnum { + + FOO, BAZ, BAR + + } + + @ConfigurationProperties(prefix = "test", ignoreUnknownFields = false) + public static class PropertyWithCharArray { + + private char[] chars; + + public char[] getChars() { + return this.chars; + } + + public void setChars(char[] chars) { + this.chars = chars; + } + + } + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithRelaxedNames { + + private String fooBar; + + private String barBAZ; + + public String getFooBar() { + return this.fooBar; + } + + public void setFooBar(String fooBar) { + this.fooBar = fooBar; + } + + public String getBarBAZ() { + return this.barBAZ; + } + + public void setBarBAZ(String barBAZ) { + this.barBAZ = barBAZ; + } + + } + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithNestedValue { + + private Nested nested = new Nested(); + + public Nested getNested() { + return this.nested; + } + + public static class Nested { + + private String value; + + public void setValue(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + } + + } + + @Validated + @ConfigurationProperties(prefix = "test") + public static class PropertiesWithMap { + + private Map map; + + public Map getMap() { + return this.map; + } + + public void setMap(Map map) { + this.map = map; + } + + } + + @ConfigurationProperties(prefix = "test") + public static class PropertiesWithComplexMap { + + private Map> map; + + public Map> getMap() { + return this.map; + } + + public void setMap(Map> map) { + this.map = map; + } + + } + + + @ConfigurationProperties(prefix = "test") + public static class PropertyWithValidatingSetter { + + private String foo; + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + if (!foo.equals("bar")) { + throw new IllegalArgumentException("Wrong value for foo"); + } + } + + } + + @ConfigurationProperties(prefix = "custom") + @Validated + public static class PropertyWithCustomValidator { + + private String foo; + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + + public static class CustomPropertyValidator implements Validator { + + @Override + public boolean supports(Class aClass) { + return aClass == PropertyWithCustomValidator.class; + } + + @Override + public void validate(Object o, Errors errors) { + ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1"); + } + + } + +} 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 dc0249e060..5c3b2454ee 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 @@ -16,11 +16,9 @@ package org.springframework.boot.context.properties; -import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -40,7 +38,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.context.properties.bind.BindException; -import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -60,15 +57,10 @@ 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; 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; /** * Tests for {@link ConfigurationPropertiesBindingPostProcessor}. @@ -96,78 +88,18 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } @Test - public void testValidationWithSetter() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "test.foo=spam"); - this.context.register(TestConfigurationWithValidatingSetter.class); - try { - this.context.refresh(); - fail("Expected exception"); - } - catch (BeanCreationException ex) { - BindException bindException = (BindException) ex.getCause(); - assertThat(bindException.getMessage()) - .startsWith("Failed to bind properties under 'test' to " - + PropertyWithValidatingSetter.class.getName()); - } - } - - @Test - public void unknownFieldFailureMessageContainsDetailsOfPropertyOrigin() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "com.example.baz=spam"); - this.context.register(TestConfiguration.class); - try { - this.context.refresh(); - fail("Expected exception"); - } - catch (BeanCreationException ex) { - BindException bindException = (BindException) ex.getCause(); - assertThat(bindException.getMessage()) - .startsWith("Failed to bind properties under 'com.example' to " - + TestConfiguration.class.getName()); - } - } - - @Test - public void testValidationWithoutJSR303() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfigurationWithoutJSR303.class); - assertBindingFailure(1); - } - - @Test - public void testValidationWithJSR303() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfigurationWithJSR303.class); - assertBindingFailure(2); - } - - @Test - public void testValidationAndNullOutValidator() { + public void binderIsNullOutAfterContextRefresh() { this.context = new AnnotationConfigApplicationContext(); this.context.register(TestConfiguration.class); this.context.refresh(); ConfigurationPropertiesBindingPostProcessor bean = this.context .getBean(ConfigurationPropertiesBindingPostProcessor.class); - assertThat(ReflectionTestUtils.getField(bean, "validator")).isNull(); + assertThat(ReflectionTestUtils.getField(bean, "configurationPropertiesBinder")) + .isNull(); } @Test - public void testSuccessfulValidationWithJSR303() { - MockEnvironment env = new MockEnvironment(); - env.setProperty("test.foo", "123456"); - env.setProperty("test.bar", "654321"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(env); - this.context.register(TestConfigurationWithJSR303.class); - this.context.refresh(); - } - - @Test - public void testSuccessfulValidationWithInterface() { + public void bindToInterfaceBean() { MockEnvironment env = new MockEnvironment(); env.setProperty("test.foo", "bar"); this.context = new AnnotationConfigApplicationContext(); @@ -179,7 +111,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } @Test - public void testInitializersSeeBoundProperties() { + public void initializerSeeBoundProperties() { MockEnvironment env = new MockEnvironment(); env.setProperty("bar", "foo"); this.context = new AnnotationConfigApplicationContext(); @@ -189,62 +121,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } @Test - public void testValidationWithCustomValidator() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfigurationWithCustomValidator.class); - assertBindingFailure(1); - } - - @Test - public void testValidationWithCustomValidatorNotSupported() { - MockEnvironment env = new MockEnvironment(); - env.setProperty("test.foo", "bar"); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(env); - this.context.register(TestConfigurationWithCustomValidator.class, - PropertyWithValidatingSetter.class); - assertBindingFailure(1); - } - - @Test - public void testPropertyWithEnum() throws Exception { - doEnumTest("test.theValue=foo"); - } - - @Test - public void testRelaxedPropertyWithEnum() throws Exception { - doEnumTest("test.the-value=FoO"); - doEnumTest("test.THE_VALUE=FoO"); - } - - private void doEnumTest(String property) { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, property); - this.context.register(PropertyWithEnum.class); - this.context.refresh(); - assertThat(this.context.getBean(PropertyWithEnum.class).getTheValue()) - .isEqualTo(FooEnum.FOO); - this.context.close(); - } - - @Test - public void testRelaxedPropertyWithSetOfEnum() { - doEnumSetTest("test.the-values=foo,bar", FooEnum.FOO, FooEnum.BAR); - doEnumSetTest("test.the-values=foo", FooEnum.FOO); - } - - private void doEnumSetTest(String property, FooEnum... expected) { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, property); - this.context.register(PropertyWithEnum.class); - this.context.refresh(); - assertThat(this.context.getBean(PropertyWithEnum.class).getTheValues()) - .contains(expected); - this.context.close(); - } - - @Test - public void testValueBindingForDefaults() throws Exception { + public void bindWithValueDefault() throws Exception { this.context = new AnnotationConfigApplicationContext(); TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "default.value=foo"); @@ -255,7 +132,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } @Test - public void configurationPropertiesWithFactoryBean() throws Exception { + public void binderShouldNotInitializeFactoryBeans() throws Exception { ConfigurationPropertiesWithFactoryBean.factoryBeanInit = false; this.context = new AnnotationConfigApplicationContext() { @Override @@ -275,57 +152,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { .isTrue(); } - @Test - public void configurationPropertiesWithCharArray() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "test.chars=word"); - this.context.register(PropertyWithCharArray.class); - this.context.refresh(); - assertThat(this.context.getBean(PropertyWithCharArray.class).getChars()) - .isEqualTo("word".toCharArray()); - } - - @Test - public void notWritablePropertyException() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "test.madeup:word"); - this.context.register(PropertyWithCharArray.class); - this.thrown.expect(BeanCreationException.class); - this.thrown.expectMessage("test"); - this.context.refresh(); - } - - @Test - public void relaxedPropertyNamesSame() throws Exception { - testRelaxedPropertyNames("test.FOO_BAR=test1", "test.FOO_BAR=test2", - "test.BAR-B-A-Z=testa", "test.BAR-B-A-Z=testb"); - } - - private void testRelaxedPropertyNames(String... environment) { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - environment); - this.context.register(RelaxedPropertyNames.class); - this.context.refresh(); - RelaxedPropertyNames bean = this.context.getBean(RelaxedPropertyNames.class); - assertThat(bean.getFooBar()).isEqualTo("test2"); - assertThat(bean.getBarBAZ()).isEqualTo("testb"); - } - - @Test - public void nestedProperties() throws Exception { - // gh-3539 - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "test.nested.value=test1"); - this.context.register(PropertyWithNestedValue.class); - this.context.refresh(); - assertThat(this.context.getBean(PropertyWithNestedValue.class).getNested() - .getValue()).isEqualTo("test1"); - } - @Test public void bindWithoutConfigurationPropertiesAnnotation() { this.context = new AnnotationConfigApplicationContext(); @@ -338,27 +164,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { this.context.refresh(); } - @Test - public void bindWithIgnoreInvalidFieldsAnnotation() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "com.example.bar=spam"); - this.context.register(TestConfigurationWithIgnoreErrors.class); - this.context.refresh(); - assertThat(this.context.getBean(TestConfigurationWithIgnoreErrors.class).getBar()) - .isEqualTo(0); - } - - @Test - public void bindWithNoIgnoreInvalidFieldsAnnotation() { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "com.example.foo=hello"); - this.context.register(TestConfiguration.class); - this.thrown.expect(BeanCreationException.class); - this.context.refresh(); - } - @Test public void multiplePropertySourcesPlaceholderConfigurer() throws Exception { this.context = new AnnotationConfigApplicationContext(); @@ -368,48 +173,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { "Multiple PropertySourcesPlaceholderConfigurer beans registered"); } - @Test - public void propertiesWithMap() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, - "test.map.foo=bar"); - this.context.register(PropertiesWithMap.class); - this.context.refresh(); - assertThat(this.context.getBean(PropertiesWithMap.class).getMap()) - .containsEntry("foo", "bar"); - } - - @Test - public void systemPropertiesShouldBindToMap() throws Exception { - MockEnvironment env = new MockEnvironment(); - MutablePropertySources propertySources = env.getPropertySources(); - propertySources.addLast(new SystemEnvironmentPropertySource("system", - Collections.singletonMap("TEST_MAP_FOO_BAR", "baz"))); - this.context = new AnnotationConfigApplicationContext(); - this.context.setEnvironment(env); - this.context.register(PropertiesWithComplexMap.class); - this.context.refresh(); - Map> map = this.context - .getBean(PropertiesWithComplexMap.class).getMap(); - Map foo = map.get("foo"); - assertThat(foo).containsEntry("bar", "baz"); - } - - @Test - public void overridingPropertiesInEnvShouldOverride() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - ConfigurableEnvironment env = this.context.getEnvironment(); - MutablePropertySources propertySources = env.getPropertySources(); - propertySources.addFirst(new SystemEnvironmentPropertySource("system", - Collections.singletonMap("COM_EXAMPLE_FOO", "10"))); - propertySources.addLast(new MapPropertySource("test", - Collections.singletonMap("com.example.foo", 5))); - this.context.register(TestConfiguration.class); - this.context.refresh(); - int foo = this.context.getBean(TestConfiguration.class).getFoo(); - assertThat(foo).isEqualTo(10); - } - @Test public void overridingPropertiesWithPlaceholderResolutionInEnvShouldOverride() throws Exception { @@ -466,19 +229,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertThat(second.getTwo()).isEqualTo("baz"); } - @Test - public void javaTimeDurationCanBeBound() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - MutablePropertySources sources = this.context.getEnvironment() - .getPropertySources(); - sources.addFirst(new MapPropertySource("test", - Collections.singletonMap("test.duration", "PT1M"))); - this.context.register(DurationProperty.class); - this.context.refresh(); - Duration duration = this.context.getBean(DurationProperty.class).getDuration(); - assertThat(duration.getSeconds()).isEqualTo(60); - } - @Test public void converterIsFound() { prepareConverterContext(ConverterConfiguration.class, PersonProperty.class); @@ -524,94 +274,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { this.context.register(config); } - private void assertBindingFailure(int errorCount) { - try { - this.context.refresh(); - fail("Expected exception"); - } - catch (BeanCreationException ex) { - assertThat(((BindValidationException) ex.getRootCause()).getValidationErrors() - .getAllErrors().size()).isEqualTo(errorCount); - } - } - - @Configuration - @EnableConfigurationProperties - public static class TestConfigurationWithValidatingSetter { - - @Bean - public PropertyWithValidatingSetter testProperties() { - return new PropertyWithValidatingSetter(); - } - - } - - @ConfigurationProperties(prefix = "test") - public static class PropertyWithValidatingSetter { - - private String foo; - - public String getFoo() { - return this.foo; - } - - public void setFoo(String foo) { - this.foo = foo; - if (!foo.equals("bar")) { - throw new IllegalArgumentException("Wrong value for foo"); - } - } - - } - - @Configuration - @EnableConfigurationProperties - public static class TestConfigurationWithoutJSR303 { - - @Bean - public PropertyWithoutJSR303 testProperties() { - return new PropertyWithoutJSR303(); - } - - } - - @ConfigurationProperties(prefix = "test") - @Validated - public static class PropertyWithoutJSR303 implements Validator { - - private String foo; - - @Override - public boolean supports(Class clazz) { - return clazz.isAssignableFrom(getClass()); - } - - @Override - public void validate(Object target, Errors errors) { - ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1"); - } - - public String getFoo() { - return this.foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } - - } - - @Configuration - @EnableConfigurationProperties - public static class TestConfigurationWithJSR303 { - - @Bean - public PropertyWithJSR303 testProperties() { - return new PropertyWithJSR303(); - } - - } - @Configuration @EnableConfigurationProperties @ConfigurationProperties @@ -660,40 +322,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } } - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "com.example", ignoreInvalidFields = true) - public static class TestConfigurationWithIgnoreErrors { - - private long bar; - - public void setBar(long bar) { - this.bar = bar; - } - - public long getBar() { - return this.bar; - } - - } - - @ConfigurationProperties(prefix = "test") - @Validated - public static class PropertyWithJSR303 extends PropertyWithoutJSR303 { - - @NotNull - private String bar; - - public void setBar(String bar) { - this.bar = bar; - } - - public String getBar() { - return this.bar; - } - - } - @Configuration @EnableConfigurationProperties public static class TestConfigurationWithValidationAndInterface { @@ -728,118 +356,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } - @Configuration - @EnableConfigurationProperties - public static class TestConfigurationWithCustomValidator { - - @Bean - public PropertyWithCustomValidator propertyWithCustomValidator() { - return new PropertyWithCustomValidator(); - } - - @Bean - public Validator configurationPropertiesValidator() { - return new CustomPropertyValidator(); - } - - } - - @ConfigurationProperties(prefix = "custom") - @Validated - public static class PropertyWithCustomValidator { - - private String foo; - - public String getFoo() { - return this.foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } - - } - - public static class CustomPropertyValidator implements Validator { - - @Override - public boolean supports(Class aClass) { - return aClass == PropertyWithCustomValidator.class; - } - - @Override - public void validate(Object o, Errors errors) { - ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1"); - } - - } - - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test", ignoreUnknownFields = false) - public static class PropertyWithCharArray { - - private char[] chars; - - public char[] getChars() { - return this.chars; - } - - public void setChars(char[] chars) { - this.chars = chars; - } - - } - - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test", ignoreUnknownFields = false) - public static class PropertyWithCharArrayExpansion { - - private char[] chars = new char[] { 'w', 'o', 'r', 'd' }; - - public char[] getChars() { - return this.chars; - } - - public void setChars(char[] chars) { - this.chars = chars; - } - - } - - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test") - public static class PropertyWithEnum { - - private FooEnum theValue; - - private List theValues; - - public void setTheValue(FooEnum value) { - this.theValue = value; - } - - public FooEnum getTheValue() { - return this.theValue; - } - - public List getTheValues() { - return this.theValues; - } - - public void setTheValues(List theValues) { - this.theValues = theValues; - } - - } - - enum FooEnum { - - FOO, BAZ, BAR - - } @Configuration @EnableConfigurationProperties @@ -865,45 +381,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } - @Configuration - @EnableConfigurationProperties - @Validated - @ConfigurationProperties(prefix = "test") - public static class PropertiesWithMap { - - @Bean - public Validator validator() { - return new LocalValidatorFactoryBean(); - } - - private Map map; - - public Map getMap() { - return this.map; - } - - public void setMap(Map map) { - this.map = map; - } - - } - - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test") - public static class PropertiesWithComplexMap { - - private Map> map; - - public Map> getMap() { - return this.map; - } - - public void setMap(Map> map) { - this.map = map; - } - - } @Configuration @EnableConfigurationProperties @@ -950,32 +427,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test") - public static class RelaxedPropertyNames { - - private String fooBar; - - private String barBAZ; - - public String getFooBar() { - return this.fooBar; - } - - public void setFooBar(String fooBar) { - this.fooBar = fooBar; - } - - public String getBarBAZ() { - return this.barBAZ; - } - - public void setBarBAZ(String barBAZ) { - this.barBAZ = barBAZ; - } - - } @SuppressWarnings("rawtypes") // Must be a raw type @@ -1003,55 +454,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test") - public static class PropertyWithNestedValue { - - private Nested nested = new Nested(); - - public Nested getNested() { - return this.nested; - } - - @Bean - public static PropertySourcesPlaceholderConfigurer configurer() { - return new PropertySourcesPlaceholderConfigurer(); - } - - public static class Nested { - - @Value("${default.value}") - private String value; - - public void setValue(String value) { - this.value = value; - } - - public String getValue() { - return this.value; - } - - } - - } - - @Configuration - @EnableConfigurationProperties - @ConfigurationProperties(prefix = "test") - public static class DurationProperty { - - private Duration duration; - - public Duration getDuration() { - return this.duration; - } - - public void setDuration(Duration duration) { - this.duration = duration; - } - - } @Configuration @EnableConfigurationProperties(PropertyWithoutConfigurationPropertiesAnnotation.class)