5 changed files with 834 additions and 5 deletions
@ -0,0 +1,201 @@
@@ -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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,339 @@
@@ -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) {} |
||||
|
||||
} |
||||
@ -0,0 +1,288 @@
@@ -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(); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue