GH-1573 Fixed BindingHandlerAdvise for validation

Fixed BindingHandlerAdvise to ensure it creates and ValidationBindHandler instead of simple BindHandler to ensure that during the merge of properties validation is not lost.
Resolves #1573
This commit is contained in:
Oleg Zhurakousky
2018-12-31 12:39:40 +01:00
parent af1a480bdd
commit b0af0205ba
4 changed files with 96 additions and 6 deletions

View File

@@ -168,7 +168,7 @@ public class ConsumerProperties {
this.partitioned = partitioned;
}
@Min(value = 1, message = "Instance count should be greater than zero.")
@Min(value = -1, message = "Instance count should be greater than or equal to -1.")
public int getInstanceCount() {
return instanceCount;
}
@@ -177,7 +177,7 @@ public class ConsumerProperties {
this.instanceCount = instanceCount;
}
@Min(value = 0, message = "Instance index should be greater than or equal to 0")
@Min(value = -1, message = "Instance index should be greater than or equal to -1")
public int getInstanceIndex() {
return instanceIndex;
}

View File

@@ -24,9 +24,12 @@ import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.Validator;
/**
*
@@ -38,19 +41,22 @@ import org.springframework.util.CollectionUtils;
public class BindingHandlerAdvise implements ConfigurationPropertiesBindHandlerAdvisor{
private final Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings;
private final Validator[] validator;
BindingHandlerAdvise(Map<ConfigurationPropertyName, ConfigurationPropertyName> additionalMappings) {
BindingHandlerAdvise(Map<ConfigurationPropertyName, ConfigurationPropertyName> additionalMappings, @Nullable Validator validator) {
this.mappings = new LinkedHashMap<>();
this.mappings.put(ConfigurationPropertyName.of("spring.cloud.stream.bindings"),
ConfigurationPropertyName.of("spring.cloud.stream.default"));
if (!CollectionUtils.isEmpty(additionalMappings)) {
this.mappings.putAll(additionalMappings);
}
this.validator = validator != null ? new Validator[] {validator} : new Validator[] {};
}
@Override
public BindHandler apply(BindHandler bindHandler) {
BindHandler handler = new BindHandler() {
BindHandler handler = new ValidationBindHandler(validator) {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
ConfigurationPropertyName defaultName = getDefaultName(name);

View File

@@ -63,6 +63,7 @@ import org.springframework.messaging.core.DestinationResolver;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
/**
@@ -92,7 +93,7 @@ public class BindingServiceConfiguration {
private Collection<DefaultBinderFactory.Listener> binderFactoryListeners;
@Bean
public BindingHandlerAdvise BindingHandlerAdvise(@Nullable MappingsProvider[] providers) {
public BindingHandlerAdvise BindingHandlerAdvise(@Nullable MappingsProvider[] providers, @Nullable Validator validator) {
Map<ConfigurationPropertyName, ConfigurationPropertyName> additionalMappings = new HashMap<>();
if (!ObjectUtils.isEmpty(providers)) {
for (int i = 0; i < providers.length; i++) {
@@ -100,7 +101,7 @@ public class BindingServiceConfiguration {
additionalMappings.putAll(mappingsProvider.getDefaultMappings());
}
}
return new BindingHandlerAdvise(additionalMappings);
return new BindingHandlerAdvise(additionalMappings, validator);
}
@Bean

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.cloud.stream.config;
import javax.validation.constraints.Min;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.context.annotation.Import;
import org.springframework.validation.annotation.Validated;
import static org.junit.Assert.assertEquals;
// see https://github.com/spring-cloud/spring-cloud-stream/issues/1573 for more details
/**
*
* @author Oleg Zhurakousky
*
*/
public class BindingHandlerAdviseTests {
@Test(expected=BeanCreationException.class)
public void testFailureWithWrongValue() {
new SpringApplicationBuilder(SampleConfiguration.class)
.web(WebApplicationType.NONE)
.run("--props.value=-1", "--spring.jmx.enabled=false");
}
@Test
public void testValidatedValueValue() {
ValidatedProps validatedProps = new SpringApplicationBuilder(SampleConfiguration.class)
.web(WebApplicationType.NONE)
.run("--props.value=2", "--spring.jmx.enabled=false").getBean(ValidatedProps.class);
assertEquals(2, validatedProps.getValue());
}
}
@EnableBinding(Sink.class)
@Import(TestChannelBinderConfiguration.class)
@EnableAutoConfiguration
@EnableConfigurationProperties(ValidatedProps.class)
class SampleConfiguration {
}
@ConfigurationProperties("props")
@Validated
class ValidatedProps {
@Min(0)
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}