Rossen Stoyanchev
8 years ago
3 changed files with 284 additions and 3 deletions
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
/* |
||||
* 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 reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.ReactiveAdapter; |
||||
import org.springframework.core.ReactiveAdapterRegistry; |
||||
import org.springframework.core.ResolvableType; |
||||
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.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 {@link Errors} or {@link BindingResult} method arguments. |
||||
* An {@code Errors} argument is expected to appear immediately after the |
||||
* model attribute in the method signature. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
||||
private final ReactiveAdapterRegistry adapterRegistry; |
||||
|
||||
|
||||
/** |
||||
* Class constructor. |
||||
* @param registry for adapting to other reactive types from and to Mono |
||||
*/ |
||||
public ErrorsMethodArgumentResolver(ReactiveAdapterRegistry registry) { |
||||
Assert.notNull(registry, "'ReactiveAdapterRegistry' is required."); |
||||
this.adapterRegistry = registry; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Return the configured {@link ReactiveAdapterRegistry}. |
||||
*/ |
||||
public ReactiveAdapterRegistry getAdapterRegistry() { |
||||
return this.adapterRegistry; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
Class<?> clazz = parameter.getParameterType(); |
||||
return Errors.class.isAssignableFrom(clazz); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context, |
||||
ServerWebExchange exchange) { |
||||
|
||||
String name = getModelAttributeName(parameter); |
||||
Object errors = context.getModel().asMap().get(BindingResult.MODEL_KEY_PREFIX + name); |
||||
|
||||
Mono<?> errorsMono; |
||||
if (Mono.class.isAssignableFrom(errors.getClass())) { |
||||
errorsMono = (Mono<?>) errors; |
||||
} |
||||
else if (Errors.class.isAssignableFrom(errors.getClass())) { |
||||
errorsMono = Mono.just(errors); |
||||
} |
||||
else { |
||||
throw new IllegalStateException( |
||||
"Unexpected Errors/BindingResult type: " + errors.getClass().getName()); |
||||
} |
||||
|
||||
return errorsMono.cast(Object.class); |
||||
} |
||||
|
||||
private String getModelAttributeName(MethodParameter parameter) { |
||||
|
||||
Assert.isTrue(parameter.getParameterIndex() > 0, |
||||
"Errors argument must be immediately after a model attribute argument."); |
||||
|
||||
int index = parameter.getParameterIndex() - 1; |
||||
MethodParameter attributeParam = new MethodParameter(parameter.getMethod(), index); |
||||
Class<?> attributeType = attributeParam.getParameterType(); |
||||
|
||||
ResolvableType type = ResolvableType.forMethodParameter(attributeParam); |
||||
ReactiveAdapter adapterTo = getAdapterRegistry().getAdapterTo(type.resolve()); |
||||
|
||||
Assert.isNull(adapterTo, "Errors/BindingResult cannot be used with an async model attribute. " + |
||||
"Either declare the model attribute without the async wrapper type " + |
||||
"or handle WebExchangeBindException through the async type."); |
||||
|
||||
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); |
||||
if (annot != null && StringUtils.hasText(annot.value())) { |
||||
return annot.value(); |
||||
} |
||||
// TODO: Conventions does not deal with async wrappers
|
||||
return ClassUtils.getShortNameAsProperty(attributeType); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
/* |
||||
* 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 org.junit.Before; |
||||
import org.junit.Test; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.core.publisher.MonoProcessor; |
||||
|
||||
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.validation.BindingResult; |
||||
import org.springframework.validation.Errors; |
||||
import org.springframework.web.bind.WebExchangeDataBinder; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.reactive.result.ResolvableMethod; |
||||
import org.springframework.web.reactive.result.method.BindingContext; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange; |
||||
import org.springframework.web.server.session.MockWebSessionManager; |
||||
import org.springframework.web.server.session.WebSessionManager; |
||||
|
||||
import static junit.framework.TestCase.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; |
||||
|
||||
/** |
||||
* Unit tests for {@link ErrorsMethodArgumentResolver}. |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class ErrorsArgumentResolverTests { |
||||
|
||||
private ErrorsMethodArgumentResolver resolver ; |
||||
|
||||
private final BindingContext bindingContext = new BindingContext(); |
||||
|
||||
private BindingResult bindingResult; |
||||
|
||||
private ServerWebExchange exchange; |
||||
|
||||
private final ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle"); |
||||
|
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
this.resolver = new ErrorsMethodArgumentResolver(new ReactiveAdapterRegistry()); |
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.POST, "/path"); |
||||
MockServerHttpResponse response = new MockServerHttpResponse(); |
||||
WebSessionManager manager = new MockWebSessionManager(); |
||||
this.exchange = new DefaultServerWebExchange(request, response, manager); |
||||
|
||||
Foo foo = new Foo(); |
||||
WebExchangeDataBinder binder = this.bindingContext.createDataBinder(this.exchange, foo, "foo"); |
||||
this.bindingResult = binder.getBindingResult(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void supports() throws Exception { |
||||
|
||||
MethodParameter parameter = parameter(forClass(Errors.class)); |
||||
assertTrue(this.resolver.supportsParameter(parameter)); |
||||
|
||||
parameter = parameter(forClass(BindingResult.class)); |
||||
assertTrue(this.resolver.supportsParameter(parameter)); |
||||
|
||||
parameter = parameter(forClassWithGenerics(Mono.class, Errors.class)); |
||||
assertFalse(this.resolver.supportsParameter(parameter)); |
||||
|
||||
parameter = parameter(forClass(String.class)); |
||||
assertFalse(this.resolver.supportsParameter(parameter)); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveErrors() throws Exception { |
||||
testResolve(this.bindingResult); |
||||
} |
||||
|
||||
@Test |
||||
public void resolveErrorsMono() throws Exception { |
||||
MonoProcessor<BindingResult> monoProcessor = MonoProcessor.create(); |
||||
monoProcessor.onNext(this.bindingResult); |
||||
testResolve(monoProcessor); |
||||
} |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void resolveErrorsAfterMonoModelAttribute() throws Exception { |
||||
MethodParameter parameter = parameter(forClass(BindingResult.class)); |
||||
this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange).blockMillis(5000); |
||||
} |
||||
|
||||
|
||||
private void testResolve(Object bindingResult) { |
||||
|
||||
String key = BindingResult.MODEL_KEY_PREFIX + "foo"; |
||||
this.bindingContext.getModel().asMap().put(key, bindingResult); |
||||
|
||||
MethodParameter parameter = parameter(forClass(Errors.class)); |
||||
|
||||
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange) |
||||
.blockMillis(5000); |
||||
|
||||
assertSame(this.bindingResult, actual); |
||||
} |
||||
|
||||
|
||||
private MethodParameter parameter(ResolvableType type) { |
||||
return this.testMethod.resolveParam(type); |
||||
} |
||||
|
||||
|
||||
private static class Foo { |
||||
|
||||
private String name; |
||||
|
||||
public Foo() { |
||||
} |
||||
|
||||
public Foo(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
void handle( |
||||
@ModelAttribute Foo foo, |
||||
Errors errors, |
||||
@ModelAttribute Mono<Foo> fooMono, |
||||
BindingResult bindingResult, |
||||
Mono<Errors> errorsMono, |
||||
String string) {} |
||||
|
||||
} |
Loading…
Reference in new issue