From 67fc5ca4335d5692707a25d22fc0045d9237bccf Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 5 Sep 2017 08:50:47 +0200 Subject: [PATCH] Create a public API to bind a ConfigurationProperties object Previously, the API used to bind a ConfigurationProperties annotated object was private to the BeanPostProcessor implementation. This commit moves most of the logic to ConfigurationPropertiesBinder. As we want this object to have the same state regardless of how it is built, a builder is now provided that detects the components that the binder needs if not specified explicitly. Closes gh-8344 --- .../ConfigurationPropertiesBinder.java | 202 ++++++ .../ConfigurationPropertiesBinderBuilder.java | 227 +++++++ ...nfigurationPropertiesBindingException.java | 56 ++ ...urationPropertiesBindingPostProcessor.java | 315 +-------- .../ValidatedLocalValidatorFactoryBean.java | 66 ++ ...igurationPropertiesBinderBuilderTests.java | 263 ++++++++ .../ConfigurationPropertiesBinderTests.java | 483 ++++++++++++++ ...onPropertiesBindingPostProcessorTests.java | 612 +----------------- 8 files changed, 1331 insertions(+), 893 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilder.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingException.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/properties/ValidatedLocalValidatorFactoryBean.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderBuilderTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinderTests.java 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,217 +200,45 @@ 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; } - private ConfigurationProperties getAnnotation(Object bean, String beanName) { - ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName, - ConfigurationProperties.class); - if (annotation == null) { - annotation = AnnotationUtils.findAnnotation(bean.getClass(), - ConfigurationProperties.class); - } - 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); - } - 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) { + private ConfigurationProperties getAnnotation(Object bean, String beanName) { + ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName, + ConfigurationProperties.class); 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; + annotation = AnnotationUtils.findAnnotation(bean.getClass(), + ConfigurationProperties.class); } - + return annotation; } - /** - * {@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); - } - } + private ConfigurationPropertiesBinder getBinder() { + if (this.configurationPropertiesBinder == null) { + this.configurationPropertiesBinder = new ConfigurationPropertiesBinderBuilder(this.applicationContext) + .withConversionService(this.conversionService) + .withValidator(this.validator) + .withPropertySources(this.propertySources).build(); } - + 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)