Reactive support for @ModelAttribute argument

Issue: SPR-14542
This commit is contained in:
Rossen Stoyanchev 2016-11-04 07:53:52 +02:00
parent 3230ca6d39
commit 816e32872a
5 changed files with 834 additions and 5 deletions

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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.
*
* <p>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}.
*
* <p>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<Object> 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<String, Object> model = context.getModel().asMap();
MonoProcessor<BindingResult> 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);
}
}
}
}

View File

@ -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<String, String> 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<Object, Foo> 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<String, Object> 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<?>, 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<Foo> mono,
@ModelAttribute @Validated Single<Foo> single,
Foo fooNotAnnotated,
String stringNotAnnotated,
Mono<Foo> monoNotAnnotated,
Mono<String> monoStringNotAnnotated) {}
}

View File

@ -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<ObjectError> getAllErrors() {
return this.bindingResult.getAllErrors();
}
@Override
public boolean hasGlobalErrors() {
return this.bindingResult.hasGlobalErrors();
}
@Override
public int getGlobalErrorCount() {
return this.bindingResult.getGlobalErrorCount();
}
@Override
public List<ObjectError> 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<FieldError> 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<FieldError> 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<String, Object> 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();
}
}