diff --git a/spring-context/src/main/java/org/springframework/validation/support/BindingAwareConcurrentModel.java b/spring-context/src/main/java/org/springframework/validation/support/BindingAwareConcurrentModel.java index eac45811dd3..201160c86c5 100644 --- a/spring-context/src/main/java/org/springframework/validation/support/BindingAwareConcurrentModel.java +++ b/spring-context/src/main/java/org/springframework/validation/support/BindingAwareConcurrentModel.java @@ -54,10 +54,10 @@ public class BindingAwareConcurrentModel extends ConcurrentModel { private void removeBindingResultIfNecessary(String key, Object value) { if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) { - String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; - BindingResult bindingResult = (BindingResult) get(bindingResultKey); - if (bindingResult != null && bindingResult.getTarget() != value) { - remove(bindingResultKey); + String resultKey = BindingResult.MODEL_KEY_PREFIX + key; + BindingResult result = (BindingResult) get(resultKey); + if (result != null && result.getTarget() != value) { + remove(resultKey); } } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index 5bb4b236a96..58414125575 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -43,6 +43,7 @@ import org.springframework.web.reactive.result.method.BindingContext; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; +import org.springframework.web.bind.WebExchangeBindException; /** * Abstract base class for argument resolvers that resolve method arguments @@ -216,7 +217,7 @@ public abstract class AbstractMessageReaderArgumentResolver { WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name); binder.validate(validationHints); if (binder.getBindingResult().hasErrors()) { - throw new ServerWebInputException("Validation failed", param); + throw new WebExchangeBindException(param, binder.getBindingResult()); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java new file mode 100644 index 00000000000..2598937f175 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2016 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.web.reactive.result.method.annotation; + +import java.lang.annotation.Annotation; +import java.util.Map; + +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; + +import org.springframework.beans.BeanUtils; +import org.springframework.core.MethodParameter; +import org.springframework.core.ReactiveAdapter; +import org.springframework.core.ReactiveAdapter.Descriptor; +import org.springframework.core.ReactiveAdapterRegistry; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.WebExchangeBindException; +import org.springframework.web.bind.WebExchangeDataBinder; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.reactive.result.method.BindingContext; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.server.ServerWebExchange; + +/** + * Resolve {@code @ModelAttribute} annotated method arguments. + * + *

Model attributes are sourced from the model, or created using a default + * constructor and then added to the model. Once created the attribute is + * populated via data binding to the request (form data, query params). + * Validation also may be applied if the argument is annotated with + * {@code @javax.validation.Valid} or Spring's own + * {@code @org.springframework.validation.annotation.Validated}. + * + *

When this handler is created with {@code useDefaultResolution=true} + * any non-simple type argument and return value is regarded as a model + * attribute with or without the presence of an {@code @ModelAttribute}. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgumentResolver { + + private final boolean useDefaultResolution; + + private final ReactiveAdapterRegistry adapterRegistry; + + + /** + * Class constructor. + * @param useDefaultResolution if "true", non-simple method arguments and + * return values are considered model attributes with or without a + * {@code @ModelAttribute} annotation present. + * @param registry for adapting to other reactive types from and to Mono + */ + public ModelAttributeMethodArgumentResolver(boolean useDefaultResolution, + ReactiveAdapterRegistry registry) { + + Assert.notNull(registry, "'ReactiveAdapterRegistry' is required."); + this.useDefaultResolution = useDefaultResolution; + this.adapterRegistry = registry; + } + + + /** + * Return the configured {@link ReactiveAdapterRegistry}. + */ + public ReactiveAdapterRegistry getAdapterRegistry() { + return this.adapterRegistry; + } + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + if (parameter.hasParameterAnnotation(ModelAttribute.class)) { + return true; + } + if (this.useDefaultResolution) { + Class clazz = parameter.getParameterType(); + ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(clazz); + if (adapter != null) { + Descriptor descriptor = adapter.getDescriptor(); + if (descriptor.isNoValue() || descriptor.isMultiValue()) { + return false; + } + clazz = ResolvableType.forMethodParameter(parameter).getGeneric(0).getRawClass(); + } + return !BeanUtils.isSimpleProperty(clazz); + } + return false; + } + + @Override + public Mono resolveArgument(MethodParameter parameter, BindingContext context, + ServerWebExchange exchange) { + + ResolvableType type = ResolvableType.forMethodParameter(parameter); + ReactiveAdapter adapterTo = getAdapterRegistry().getAdapterTo(type.resolve()); + Class valueType = (adapterTo != null ? type.resolveGeneric(0) : parameter.getParameterType()); + String name = getAttributeName(valueType, parameter); + Mono valueMono = getAttributeMono(name, valueType, parameter, context, exchange); + + Map model = context.getModel().asMap(); + MonoProcessor bindingResultMono = MonoProcessor.create(); + model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultMono); + + return valueMono.then(value -> { + WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name); + return binder.bind(exchange) + .doOnError(bindingResultMono::onError) + .doOnSuccess(aVoid -> { + validateIfApplicable(binder, parameter); + BindingResult errors = binder.getBindingResult(); + model.put(BindingResult.MODEL_KEY_PREFIX + name, errors); + model.put(name, value); + bindingResultMono.onNext(errors); + }) + .then(Mono.fromCallable(() -> { + BindingResult errors = binder.getBindingResult(); + if (adapterTo != null) { + return adapterTo.fromPublisher(errors.hasErrors() ? + Mono.error(new WebExchangeBindException(parameter, errors)) : + Mono.just(value)); + } + else { + if (errors.hasErrors() && checkErrorsArgument(parameter)) { + throw new WebExchangeBindException(parameter, errors); + } + return value; + } + })); + }); + } + + private String getAttributeName(Class valueType, MethodParameter parameter) { + ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); + String name = (ann != null ? ann.value() : null); + // TODO: Conventions does not deal with async wrappers + return StringUtils.hasText(name) ? name : ClassUtils.getShortNameAsProperty(valueType); + } + + private Mono getAttributeMono(String attributeName, Class attributeType, + MethodParameter param, BindingContext context, ServerWebExchange exchange) { + + Object attribute = context.getModel().asMap().get(attributeName); + if (attribute == null) { + attribute = createAttribute(attributeName, attributeType, param, context, exchange); + } + if (attribute != null) { + ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapterFrom(null, attribute); + if (adapterFrom != null) { + return adapterFrom.toMono(attribute); + } + } + return Mono.justOrEmpty(attribute); + } + + protected Object createAttribute(String attributeName, Class attributeType, + MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { + + return BeanUtils.instantiateClass(attributeType); + } + + protected boolean checkErrorsArgument(MethodParameter methodParam) { + int i = methodParam.getParameterIndex(); + Class[] paramTypes = methodParam.getMethod().getParameterTypes(); + return paramTypes.length <= (i + 1) || !Errors.class.isAssignableFrom(paramTypes[i + 1]); + } + + protected void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) { + Annotation[] annotations = parameter.getParameterAnnotations(); + for (Annotation ann : annotations) { + Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class); + if (validAnnot != null || ann.annotationType().getSimpleName().startsWith("Valid")) { + Object hints = (validAnnot != null ? validAnnot.value() : AnnotationUtils.getValue(ann)); + Object hintArray = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); + binder.validate(hintArray); + } + } + } + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java new file mode 100644 index 00000000000..5efef3e3fc8 --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java @@ -0,0 +1,339 @@ +/* + * Copyright 2002-2016 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.web.reactive.result.method.annotation; + +import java.util.Map; +import java.util.function.Function; + +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import rx.RxReactiveStreams; +import rx.Single; + +import org.springframework.core.MethodParameter; +import org.springframework.core.ReactiveAdapterRegistry; +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.bind.WebExchangeBindException; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.reactive.result.ResolvableMethod; +import org.springframework.web.reactive.result.method.BindingContext; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.adapter.DefaultServerWebExchange; +import org.springframework.web.server.session.MockWebSessionManager; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.springframework.core.ResolvableType.forClass; +import static org.springframework.core.ResolvableType.forClassWithGenerics; +import static org.springframework.util.Assert.isTrue; + + +/** + * Unit tests for {@link ModelAttributeMethodArgumentResolver}. + * @author Rossen Stoyanchev + */ +public class ModelAttributeMethodArgumentResolverTests { + + private ServerWebExchange exchange; + + private final MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.POST, "/path"); + + private final MultiValueMap formData = new LinkedMultiValueMap<>(); + + private BindingContext bindingContext; + + private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle"); + + + @Before + public void setUp() throws Exception { + MockServerHttpResponse response = new MockServerHttpResponse(); + this.exchange = new DefaultServerWebExchange(this.request, response, new MockWebSessionManager()); + this.exchange = this.exchange.mutate().setFormData(Mono.just(this.formData)).build(); + + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); + initializer.setValidator(validator); + this.bindingContext = new BindingContext(initializer); + } + + + @Test + public void supports() throws Exception { + + ModelAttributeMethodArgumentResolver resolver = + new ModelAttributeMethodArgumentResolver(false, new ReactiveAdapterRegistry()); + + ResolvableType type = forClass(Foo.class); + assertTrue(resolver.supportsParameter(parameter(type))); + + type = forClassWithGenerics(Mono.class, Foo.class); + assertTrue(resolver.supportsParameter(parameter(type))); + + type = forClass(Foo.class); + assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + + type = forClassWithGenerics(Mono.class, Foo.class); + assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + } + + @Test + public void supportsWithDefaultResolution() throws Exception { + + ModelAttributeMethodArgumentResolver resolver = + new ModelAttributeMethodArgumentResolver(true, new ReactiveAdapterRegistry()); + + ResolvableType type = forClass(Foo.class); + assertTrue(resolver.supportsParameter(parameterNotAnnotated(type))); + + type = forClassWithGenerics(Mono.class, Foo.class); + assertTrue(resolver.supportsParameter(parameterNotAnnotated(type))); + + type = forClass(String.class); + assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + + type = forClassWithGenerics(Mono.class, String.class); + assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + } + + @Test + public void createAndBind() throws Exception { + testBindFoo(forClass(Foo.class), value -> { + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + } + + @Test + public void createAndBindToMono() throws Exception { + testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> { + assertTrue(mono.getClass().getName(), mono instanceof Mono); + Object value = ((Mono) mono).blockMillis(5000); + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + } + + @Test + public void createAndBindToSingle() throws Exception { + testBindFoo(forClassWithGenerics(Single.class, Foo.class), single -> { + assertTrue(single.getClass().getName(), single instanceof Single); + Object value = ((Single) single).toBlocking().value(); + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + } + + @Test + public void bindExisting() throws Exception { + Foo foo = new Foo(); + foo.setName("Jim"); + this.bindingContext.getModel().addAttribute(foo); + + testBindFoo(forClass(Foo.class), value -> { + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + + assertSame(foo, this.bindingContext.getModel().asMap().get("foo")); + } + + @Test + public void bindExistingMono() throws Exception { + Foo foo = new Foo(); + foo.setName("Jim"); + this.bindingContext.getModel().addAttribute("foo", Mono.just(foo)); + + testBindFoo(forClass(Foo.class), value -> { + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + + assertSame(foo, this.bindingContext.getModel().asMap().get("foo")); + } + + @Test + public void bindExistingSingle() throws Exception { + Foo foo = new Foo(); + foo.setName("Jim"); + this.bindingContext.getModel().addAttribute("foo", Single.just(foo)); + + testBindFoo(forClass(Foo.class), value -> { + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + + assertSame(foo, this.bindingContext.getModel().asMap().get("foo")); + } + + @Test + public void bindExistingMonoToMono() throws Exception { + Foo foo = new Foo(); + foo.setName("Jim"); + this.bindingContext.getModel().addAttribute("foo", Mono.just(foo)); + + testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> { + assertTrue(mono.getClass().getName(), mono instanceof Mono); + Object value = ((Mono) mono).blockMillis(5000); + assertEquals(Foo.class, value.getClass()); + return (Foo) value; + }); + } + + @Test + public void validationError() throws Exception { + testValidationError(forClass(Foo.class), resolvedArgumentMono -> resolvedArgumentMono); + } + + @Test + @SuppressWarnings("unchecked") + public void validationErrorToMono() throws Exception { + testValidationError(forClassWithGenerics(Mono.class, Foo.class), + resolvedArgumentMono -> { + Object value = resolvedArgumentMono.blockMillis(5000); + assertNotNull(value); + isTrue(value instanceof Mono); + return (Mono) value; + }); + } + + @Test + @SuppressWarnings("unchecked") + public void validationErrorToSingle() throws Exception { + testValidationError(forClassWithGenerics(Single.class, Foo.class), + resolvedArgumentMono -> { + Object value = resolvedArgumentMono.blockMillis(5000); + assertNotNull(value); + isTrue(value instanceof Single); + return Mono.from(RxReactiveStreams.toPublisher((Single) value)); + }); + } + + + private void testBindFoo(ResolvableType type, Function valueExtractor) { + + this.formData.add("name", "Robert"); + this.formData.add("age", "25"); + + Object value = createResolver() + .resolveArgument(parameter(type), this.bindingContext, this.exchange) + .blockMillis(5000); + + Foo foo = valueExtractor.apply(value); + assertEquals("Robert", foo.getName()); + + String key = "foo"; + String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; + + Map map = bindingContext.getModel().asMap(); + assertEquals(map.toString(), 2, map.size()); + assertSame(foo, map.get(key)); + assertNotNull(map.get(bindingResultKey)); + assertTrue(map.get(bindingResultKey) instanceof BindingResult); + } + + private void testValidationError(ResolvableType type, Function, Mono> valueMonoExtractor) { + + this.formData.add("age", "invalid"); + + HandlerMethodArgumentResolver resolver = createResolver(); + Mono mono = resolver.resolveArgument(parameter(type), this.bindingContext, this.exchange); + + mono = valueMonoExtractor.apply(mono); + + StepVerifier.create(mono) + .consumeErrorWith(ex -> { + assertTrue(ex instanceof WebExchangeBindException); + WebExchangeBindException bindException = (WebExchangeBindException) ex; + assertEquals(1, bindException.getErrorCount()); + assertTrue(bindException.hasFieldErrors("age")); + }) + .verify(); + } + + + private ModelAttributeMethodArgumentResolver createResolver() { + return new ModelAttributeMethodArgumentResolver(false, new ReactiveAdapterRegistry()); + } + + private MethodParameter parameter(ResolvableType type) { + return this.testMethod.resolveParam(type, + parameter -> parameter.hasParameterAnnotation(ModelAttribute.class)); + } + + private MethodParameter parameterNotAnnotated(ResolvableType type) { + return this.testMethod.resolveParam(type, + parameter -> !parameter.hasParameterAnnotations()); + } + + + private static class Foo { + + private String name; + + private int age; + + public Foo() { + } + + public Foo(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return this.age; + } + + public void setAge(int age) { + this.age = age; + } + } + + @SuppressWarnings("unused") + void handle( + @ModelAttribute @Validated Foo foo, + @ModelAttribute @Validated Mono mono, + @ModelAttribute @Validated Single single, + Foo fooNotAnnotated, + String stringNotAnnotated, + Mono monoNotAnnotated, + Mono monoStringNotAnnotated) {} + +} diff --git a/spring-web/src/main/java/org/springframework/web/bind/WebExchangeBindException.java b/spring-web/src/main/java/org/springframework/web/bind/WebExchangeBindException.java new file mode 100644 index 00000000000..99182fa8850 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/WebExchangeBindException.java @@ -0,0 +1,288 @@ +/* + * Copyright 2002-2016 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.web.bind; + +import java.beans.PropertyEditor; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.core.MethodParameter; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.server.ServerWebInputException; + +/** + * A specialization of {@link ServerWebInputException} thrown when after data + * binding and validation failure. Implements {@link BindingResult} (and its + * super-interface {@link Errors}) to allow for direct analysis of binding and + * validation errors. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +@SuppressWarnings("serial") +public class WebExchangeBindException extends ServerWebInputException implements BindingResult { + + private final BindingResult bindingResult; + + + public WebExchangeBindException(MethodParameter parameter, BindingResult bindingResult) { + super("Validation failure", parameter); + this.bindingResult = bindingResult; + } + + + /** + * Return the BindingResult that this BindException wraps. + * Will typically be a BeanPropertyBindingResult. + * @see BeanPropertyBindingResult + */ + public final BindingResult getBindingResult() { + return this.bindingResult; + } + + + @Override + public String getObjectName() { + return this.bindingResult.getObjectName(); + } + + @Override + public void setNestedPath(String nestedPath) { + this.bindingResult.setNestedPath(nestedPath); + } + + @Override + public String getNestedPath() { + return this.bindingResult.getNestedPath(); + } + + @Override + public void pushNestedPath(String subPath) { + this.bindingResult.pushNestedPath(subPath); + } + + @Override + public void popNestedPath() throws IllegalStateException { + this.bindingResult.popNestedPath(); + } + + + @Override + public void reject(String errorCode) { + this.bindingResult.reject(errorCode); + } + + @Override + public void reject(String errorCode, String defaultMessage) { + this.bindingResult.reject(errorCode, defaultMessage); + } + + @Override + public void reject(String errorCode, Object[] errorArgs, String defaultMessage) { + this.bindingResult.reject(errorCode, errorArgs, defaultMessage); + } + + @Override + public void rejectValue(String field, String errorCode) { + this.bindingResult.rejectValue(field, errorCode); + } + + @Override + public void rejectValue(String field, String errorCode, String defaultMessage) { + this.bindingResult.rejectValue(field, errorCode, defaultMessage); + } + + @Override + public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { + this.bindingResult.rejectValue(field, errorCode, errorArgs, defaultMessage); + } + + @Override + public void addAllErrors(Errors errors) { + this.bindingResult.addAllErrors(errors); + } + + + @Override + public boolean hasErrors() { + return this.bindingResult.hasErrors(); + } + + @Override + public int getErrorCount() { + return this.bindingResult.getErrorCount(); + } + + @Override + public List getAllErrors() { + return this.bindingResult.getAllErrors(); + } + + @Override + public boolean hasGlobalErrors() { + return this.bindingResult.hasGlobalErrors(); + } + + @Override + public int getGlobalErrorCount() { + return this.bindingResult.getGlobalErrorCount(); + } + + @Override + public List getGlobalErrors() { + return this.bindingResult.getGlobalErrors(); + } + + @Override + public ObjectError getGlobalError() { + return this.bindingResult.getGlobalError(); + } + + @Override + public boolean hasFieldErrors() { + return this.bindingResult.hasFieldErrors(); + } + + @Override + public int getFieldErrorCount() { + return this.bindingResult.getFieldErrorCount(); + } + + @Override + public List getFieldErrors() { + return this.bindingResult.getFieldErrors(); + } + + @Override + public FieldError getFieldError() { + return this.bindingResult.getFieldError(); + } + + @Override + public boolean hasFieldErrors(String field) { + return this.bindingResult.hasFieldErrors(field); + } + + @Override + public int getFieldErrorCount(String field) { + return this.bindingResult.getFieldErrorCount(field); + } + + @Override + public List getFieldErrors(String field) { + return this.bindingResult.getFieldErrors(field); + } + + @Override + public FieldError getFieldError(String field) { + return this.bindingResult.getFieldError(field); + } + + @Override + public Object getFieldValue(String field) { + return this.bindingResult.getFieldValue(field); + } + + @Override + public Class getFieldType(String field) { + return this.bindingResult.getFieldType(field); + } + + @Override + public Object getTarget() { + return this.bindingResult.getTarget(); + } + + @Override + public Map getModel() { + return this.bindingResult.getModel(); + } + + @Override + public Object getRawFieldValue(String field) { + return this.bindingResult.getRawFieldValue(field); + } + + @Override + @SuppressWarnings("rawtypes") + public PropertyEditor findEditor(String field, Class valueType) { + return this.bindingResult.findEditor(field, valueType); + } + + @Override + public PropertyEditorRegistry getPropertyEditorRegistry() { + return this.bindingResult.getPropertyEditorRegistry(); + } + + @Override + public void addError(ObjectError error) { + this.bindingResult.addError(error); + } + + @Override + public String[] resolveMessageCodes(String errorCode) { + return this.bindingResult.resolveMessageCodes(errorCode); + } + + @Override + public String[] resolveMessageCodes(String errorCode, String field) { + return this.bindingResult.resolveMessageCodes(errorCode, field); + } + + @Override + public void recordSuppressedField(String field) { + this.bindingResult.recordSuppressedField(field); + } + + @Override + public String[] getSuppressedFields() { + return this.bindingResult.getSuppressedFields(); + } + + + /** + * Returns diagnostic information about the errors held in this object. + */ + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public String getMessage() { + MethodParameter parameter = getMethodParameter().get(); + StringBuilder sb = new StringBuilder("Validation failed for argument at index ") + .append(parameter.getParameterIndex()).append(" in method: ") + .append(parameter.getMethod().toGenericString()) + .append(", with ").append(this.bindingResult.getErrorCount()).append(" error(s): "); + for (ObjectError error : this.bindingResult.getAllErrors()) { + sb.append("[").append(error).append("] "); + } + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return (this == other || this.bindingResult.equals(other)); + } + + @Override + public int hashCode() { + return this.bindingResult.hashCode(); + } + +}