Commit be1dc647 authored by Christian Dupuis's avatar Christian Dupuis

Beans annotated with @ConfigurationProperties can be their own validators when...

Beans annotated with @ConfigurationProperties can be their own validators when implementing Spring's Validator interface

fixes #255
parent bfff4654
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.
......@@ -49,8 +49,10 @@ import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
......@@ -60,6 +62,7 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
*
* @author Dave Syer
* @author Phillip Webb
* @author Christian Dupuis
*/
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, ApplicationContextAware,
......@@ -294,7 +297,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
else {
factory.setPropertySources(this.propertySources);
}
factory.setValidator(this.validator);
factory.setValidator(determineValidator(bean));
// If no explicit conversion service is provided we add one so that (at least)
// comma-separated arrays of convertibles can be bound automatically
factory.setConversionService(this.conversionService == null ? getDefaultConversionService()
......@@ -318,6 +321,16 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
}
}
private Validator determineValidator(Object bean) {
if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {
if (this.validator == null) {
return (Validator) bean;
}
return new ChainingValidator(this.validator, (Validator) bean);
}
return this.validator;
}
private PropertySources loadPropertySources(String[] path) {
MutablePropertySources propertySources = new MutablePropertySources();
PropertySourceLoader[] loaders = {
......@@ -365,4 +378,38 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
}
/**
* {@link Validator} implementation that wraps {@link Validator} instances and chains
* their execution.
*/
private static class ChainingValidator implements Validator {
private Validator[] validators;
public 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);
}
}
}
}
}
/*
* Copyright 2012-2014 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 javax.validation.constraints.NotNull;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertTrue;
/**
*
* Tests for {@link ConfigurationPropertiesBindingPostProcessor}.
*
* @author Christian Dupuis
*/
public class ConfigurationPropertiesBindingPostProcessorTests {
private AnnotationConfigWebApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void testValidationWithoutJSR303() {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(TestConfigurationWithoutJSR303.class);
try {
this.context.refresh();
}
catch (BeanCreationException ex) {
BindException bex = (BindException) ex.getRootCause();
assertTrue(1 == bex.getErrorCount());
}
}
@Test
public void testValidationWithJSR303() {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(TestConfigurationWithJSR303.class);
try {
this.context.refresh();
}
catch (BeanCreationException ex) {
BindException bex = (BindException) ex.getRootCause();
assertTrue(2 == bex.getErrorCount());
}
}
@Test
public void testSuccessfulValidationWithJSR303() {
MockEnvironment env = new MockEnvironment();
env.setProperty("test.foo", "123456");
env.setProperty("test.bar", "654321");
this.context = new AnnotationConfigWebApplicationContext();
this.context.setEnvironment(env);
this.context.register(TestConfigurationWithJSR303.class);
this.context.refresh();
}
@Configuration
@EnableConfigurationProperties
public static class TestConfigurationWithoutJSR303 {
@Bean
public PropertyWithoutJSR303 testProperties() {
return new PropertyWithoutJSR303();
}
}
@ConfigurationProperties(name = "test")
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();
}
}
@ConfigurationProperties(name = "test")
public static class PropertyWithJSR303 extends PropertyWithoutJSR303 {
@NotNull
private String bar;
public void setBar(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment