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-8344pull/10168/head
parent
9242def4c0
commit
67fc5ca433
@ -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<PropertySource<?>> propertySources;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Validator validator;
|
||||
|
||||
private Iterable<ConfigurationPropertySource> configurationSources;
|
||||
|
||||
ConfigurationPropertiesBinder(Iterable<PropertySource<?>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<PropertySource<?>> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<PropertySource<?>> 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> T getOptionalBean(String name, Class<T> type) {
|
||||
try {
|
||||
return this.applicationContext.getBean(name, type);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConversionServiceFactory {
|
||||
|
||||
private List<Converter<?, ?>> converters = Collections.emptyList();
|
||||
|
||||
private List<GenericConverter> 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<Converter<?, ?>> 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<GenericConverter> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<String, Address> {
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<FooEnum> theValues;
|
||||
|
||||
public void setTheValue(FooEnum value) {
|
||||
this.theValue = value;
|
||||
}
|
||||
|
||||
public FooEnum getTheValue() {
|
||||
return this.theValue;
|
||||
}
|
||||
|
||||
public List<FooEnum> getTheValues() {
|
||||
return this.theValues;
|
||||
}
|
||||
|
||||
public void setTheValues(List<FooEnum> 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<String, String> map;
|
||||
|
||||
public Map<String, String> getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public void setMap(Map<String, String> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
public static class PropertiesWithComplexMap {
|
||||
|
||||
private Map<String, Map<String, String>> map;
|
||||
|
||||
public Map<String, Map<String, String>> getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public void setMap(Map<String, Map<String, String>> 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue