Revised InvocableHandlerMethod exception messages (controller vs endpoint vs handler)

Introduces dedicated MethodArgumentResolutionException for spring-messaging invocations.

Issue: SPR-15139
This commit is contained in:
Juergen Hoeller
2017-01-16 21:14:46 +01:00
parent 74596a6f1e
commit 047786acef
10 changed files with 223 additions and 188 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -45,6 +45,7 @@ import org.springframework.web.server.ServerWebExchange;
* a {@link ServerWebExchange} and use that to invoke the underlying method.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public class InvocableHandlerMethod extends HandlerMethod {
@@ -89,14 +90,13 @@ public class InvocableHandlerMethod extends HandlerMethod {
/**
* Invoke the method for the given exchange.
*
* @param exchange the current exchange
* @param bindingContext the binding context to use
* @param providedArgs optional list of argument values to match by type
* @return Mono with a {@link HandlerResult}.
*/
public Mono<HandlerResult> invoke(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
public Mono<HandlerResult> invoke(ServerWebExchange exchange, BindingContext bindingContext,
Object... providedArgs) {
return resolveArguments(exchange, bindingContext, providedArgs).then(args -> {
try {
@@ -108,14 +108,13 @@ public class InvocableHandlerMethod extends HandlerMethod {
return Mono.error(ex.getTargetException());
}
catch (Throwable ex) {
String msg = getInvocationErrorMessage(args);
return Mono.error(new IllegalStateException(msg));
return Mono.error(new IllegalStateException(getInvocationErrorMessage(args)));
}
});
}
private Mono<Object[]> resolveArguments(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, BindingContext bindingContext,
Object... providedArgs) {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
@@ -125,7 +124,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
.map(param -> {
param.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(param, getBean().getClass());
return findProvidedArg(param, providedArgs)
return findProvidedArgument(param, providedArgs)
.map(Mono::just)
.orElseGet(() -> {
HandlerMethodArgumentResolver resolver = findResolver(param);
@@ -144,12 +143,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
}
private Optional<Object> findProvidedArg(MethodParameter param, Object... providedArgs) {
private Optional<Object> findProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (ObjectUtils.isEmpty(providedArgs)) {
return Optional.empty();
}
return Arrays.stream(providedArgs)
.filter(arg -> param.getParameterType().isInstance(arg))
.filter(arg -> parameter.getParameterType().isInstance(arg))
.findFirst();
}
@@ -157,34 +156,33 @@ public class InvocableHandlerMethod extends HandlerMethod {
return this.resolvers.stream()
.filter(r -> r.supportsParameter(param))
.findFirst()
.orElseThrow(() -> getArgumentError("No resolver for ", param, null));
.orElseThrow(() -> getArgumentError("No suitable resolver for", param, null));
}
private Mono<Object> resolveArg(HandlerMethodArgumentResolver resolver, MethodParameter param,
private Mono<Object> resolveArg(HandlerMethodArgumentResolver resolver, MethodParameter parameter,
BindingContext bindingContext, ServerWebExchange exchange) {
try {
return resolver.resolveArgument(param, bindingContext, exchange)
return resolver.resolveArgument(parameter, bindingContext, exchange)
.defaultIfEmpty(NO_ARG_VALUE)
.doOnError(cause -> {
if(logger.isDebugEnabled()) {
logger.debug(getDetailedErrorMessage("Error resolving ", param), cause);
logger.debug(getDetailedErrorMessage("Failed to resolve", parameter), cause);
}
});
}
catch (Exception ex) {
throw getArgumentError("Error resolving ", param, ex);
throw getArgumentError("Failed to resolve", parameter, ex);
}
}
private IllegalStateException getArgumentError(String message, MethodParameter param, Throwable ex) {
return new IllegalStateException(getDetailedErrorMessage(message, param), ex);
private IllegalStateException getArgumentError(String text, MethodParameter parameter, Throwable ex) {
return new IllegalStateException(getDetailedErrorMessage(text, parameter), ex);
}
private String getDetailedErrorMessage(String message, MethodParameter param) {
return message + "argument [" + param.getParameterIndex() + "] " +
"of type [" + param.getParameterType().getName() + "] " +
"on method [" + getBridgedMethod().toGenericString() + "]";
private String getDetailedErrorMessage(String text, MethodParameter param) {
return text + " argument " + param.getParameterIndex() + " of type '" +
param.getParameterType().getName() + "' on " + getBridgedMethod().toGenericString();
}
private Object doInvoke(Object[] args) throws Exception {
@@ -207,8 +205,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
"[" + i + "][type=" + args[i].getClass().getName() + "][value=" + args[i] + "]" :
"[" + i + "][null]"))
.collect(Collectors.joining(",", " ", " "));
return "Failed to invoke controller with resolved arguments:" + argumentDetails +
"on method [" + getBridgedMethod().toGenericString() + "]";
return "Failed to invoke handler method with resolved arguments:" + argumentDetails +
"on " + getBridgedMethod().toGenericString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -34,18 +34,16 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link InvocableHandlerMethod}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class InvocableHandlerMethodTests {
@@ -93,14 +91,14 @@ public class InvocableHandlerMethodTests {
InvocableHandlerMethod hm = handlerMethod("singleArg");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("No resolver for argument [0] of type [java.lang.String] " +
"on method [" + hm.getMethod().toGenericString() + "]"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("No suitable resolver for argument 0 of type 'java.lang.String' " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
@@ -109,13 +107,13 @@ public class InvocableHandlerMethodTests {
addResolver(hm, Mono.error(new UnsupportedMediaTypeStatusException("boo")));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(UnsupportedMediaTypeStatusException.class));
assertThat(error.getMessage(), is("Request failure [status: 415, reason: \"boo\"]"));
})
.verify();
try {
mono.block();
fail("Expected UnsupportedMediaTypeStatusException");
}
catch (UnsupportedMediaTypeStatusException ex) {
assertThat(ex.getMessage(), is("Request failure [status: 415, reason: \"boo\"]"));
}
}
@Test
@@ -124,15 +122,15 @@ public class InvocableHandlerMethodTests {
addResolver(hm, Mono.just(1));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("Failed to invoke controller with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on method [" + hm.getMethod().toGenericString() + "]"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("Failed to invoke handler method with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
@@ -140,13 +138,13 @@ public class InvocableHandlerMethodTests {
InvocableHandlerMethod hm = handlerMethod("exceptionMethod");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
StepVerifier.create(mono)
.expectNextCount(0)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), is("boo"));
})
.verify();
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("boo"));
}
}