Revised InvocableHandlerMethod exception messages (controller vs endpoint vs handler)
Introduces dedicated MethodArgumentResolutionException for spring-messaging invocations. Issue: SPR-15139
This commit is contained in:
		
							parent
							
								
									74596a6f1e
								
							
						
					
					
						commit
						047786acef
					
				|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 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. | ||||
|  | @ -18,7 +18,7 @@ package org.springframework.messaging.handler.annotation.support; | |||
| 
 | ||||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.messaging.Message; | ||||
| import org.springframework.messaging.MessagingException; | ||||
| import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException; | ||||
| 
 | ||||
| /** | ||||
|  * Base class for exceptions resulting from the invocation of | ||||
|  | @ -26,43 +26,24 @@ import org.springframework.messaging.MessagingException; | |||
|  * | ||||
|  * @author Rossen Stoyanchev | ||||
|  * @since 4.0.3 | ||||
|  * @deprecated as of 4.3.6, in favor of the invocation-associated | ||||
|  * {@link MethodArgumentResolutionException} | ||||
|  */ | ||||
| @Deprecated | ||||
| @SuppressWarnings("serial") | ||||
| public abstract class AbstractMethodArgumentResolutionException extends MessagingException { | ||||
| public abstract class AbstractMethodArgumentResolutionException extends MethodArgumentResolutionException { | ||||
| 
 | ||||
| 	private final MethodParameter parameter; | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance providing the invalid {@code MethodParameter}. | ||||
| 	 */ | ||||
| 	protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter parameter) { | ||||
| 		this(message, parameter, getMethodParamMessage(parameter)); | ||||
| 		super(message, parameter); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance providing the invalid {@code MethodParameter} and | ||||
| 	 * a prepared description. Subclasses should prepend the description with | ||||
| 	 * the help of {@link #getMethodParamMessage(org.springframework.core.MethodParameter)}. | ||||
| 	 */ | ||||
| 	protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter param, String description) { | ||||
| 		super(message, description); | ||||
| 		this.parameter = param; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the MethodParameter that was rejected. | ||||
| 	 */ | ||||
| 	public final MethodParameter getMethodParameter() { | ||||
| 		return this.parameter; | ||||
| 	protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter parameter, String description) { | ||||
| 		super(message, parameter, description); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	protected static String getMethodParamMessage(MethodParameter param) { | ||||
| 		return new StringBuilder("Could not resolve method parameter at index ") | ||||
| 				.append(param.getParameterIndex()).append(" in method: ") | ||||
| 				.append(param.getMethod().toGenericString()).append(" ").toString(); | ||||
| 		return ""; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 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. | ||||
|  | @ -29,27 +29,25 @@ import org.springframework.validation.ObjectError; | |||
|  * @author Rossen Stoyanchev | ||||
|  * @since 4.0.1 | ||||
|  */ | ||||
| @SuppressWarnings("serial") | ||||
| @SuppressWarnings({"serial", "deprecation"}) | ||||
| public class MethodArgumentNotValidException extends AbstractMethodArgumentResolutionException { | ||||
| 
 | ||||
| 	private final BindingResult bindingResult; | ||||
| 	private BindingResult bindingResult; | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance with the invalid {@code MethodParameter}. | ||||
| 	 */ | ||||
| 	public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter) { | ||||
| 		this(message, parameter, null); | ||||
| 		super(message, parameter); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance with the invalid {@code MethodParameter} and a | ||||
| 	 * {@link org.springframework.validation.BindingResult}. | ||||
| 	 */ | ||||
| 	public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter, | ||||
| 			BindingResult bindingResult) { | ||||
| 
 | ||||
| 		super(message, parameter, getMethodParamMessage(parameter) + getValidationErrorMessage(bindingResult)); | ||||
| 	public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter, BindingResult bindingResult) { | ||||
| 		super(message, parameter, getValidationErrorMessage(bindingResult)); | ||||
| 		this.bindingResult = bindingResult; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -64,17 +62,12 @@ public class MethodArgumentNotValidException extends AbstractMethodArgumentResol | |||
| 
 | ||||
| 
 | ||||
| 	private static String getValidationErrorMessage(BindingResult bindingResult) { | ||||
| 		if (bindingResult != null) { | ||||
| 			StringBuilder sb = new StringBuilder(); | ||||
| 			sb.append(", with ").append(bindingResult.getErrorCount()).append(" error(s): "); | ||||
| 			for (ObjectError error : bindingResult.getAllErrors()) { | ||||
| 				sb.append("[").append(error).append("] "); | ||||
| 			} | ||||
| 			return sb.toString(); | ||||
| 		} | ||||
| 		else { | ||||
| 			return ""; | ||||
| 		StringBuilder sb = new StringBuilder(); | ||||
| 		sb.append(bindingResult.getErrorCount()).append(" error(s): "); | ||||
| 		for (ObjectError error : bindingResult.getAllErrors()) { | ||||
| 			sb.append("[").append(error).append("] "); | ||||
| 		} | ||||
| 		return sb.toString(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2002-2015 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. | ||||
|  | @ -20,20 +20,16 @@ import org.springframework.core.MethodParameter; | |||
| import org.springframework.messaging.Message; | ||||
| 
 | ||||
| /** | ||||
|  * Exception that indicates that a method argument has not the | ||||
|  * expected type. | ||||
|  * Exception that indicates that a method argument has not the expected type. | ||||
|  * | ||||
|  * @author Stephane Nicoll | ||||
|  * @since 4.0.3 | ||||
|  */ | ||||
| @SuppressWarnings("serial") | ||||
| @SuppressWarnings({"serial", "deprecation"}) | ||||
| public class MethodArgumentTypeMismatchException extends AbstractMethodArgumentResolutionException { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance with the invalid {@code MethodParameter}. | ||||
| 	 */ | ||||
| 	public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter param, String description) { | ||||
| 		super(message, param, getMethodParamMessage(param) + description); | ||||
| 	public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter parameter, String description) { | ||||
| 		super(message, parameter, description); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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. | ||||
|  | @ -139,36 +139,22 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 				} | ||||
| 				catch (Exception ex) { | ||||
| 					if (logger.isDebugEnabled()) { | ||||
| 						logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); | ||||
| 						logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); | ||||
| 					} | ||||
| 					throw ex; | ||||
| 				} | ||||
| 			} | ||||
| 			if (args[i] == null) { | ||||
| 				String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); | ||||
| 				throw new IllegalStateException(msg); | ||||
| 				throw new MethodArgumentResolutionException(message, parameter, | ||||
| 						getArgumentResolutionErrorMessage("No suitable resolver for", i)); | ||||
| 			} | ||||
| 		} | ||||
| 		return args; | ||||
| 	} | ||||
| 
 | ||||
| 	private String getArgumentResolutionErrorMessage(String message, int index) { | ||||
| 		MethodParameter param = getMethodParameters()[index]; | ||||
| 		message += " [" + index + "] [type=" + param.getParameterType().getName() + "]"; | ||||
| 		return getDetailedErrorMessage(message); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Adds HandlerMethod details such as the controller type and method | ||||
| 	 * signature to the given error message. | ||||
| 	 * @param message error message to append the HandlerMethod details to | ||||
| 	 */ | ||||
| 	protected String getDetailedErrorMessage(String message) { | ||||
| 		StringBuilder sb = new StringBuilder(message).append("\n"); | ||||
| 		sb.append("HandlerMethod details: \n"); | ||||
| 		sb.append("Controller [").append(getBeanType().getName()).append("]\n"); | ||||
| 		sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); | ||||
| 		return sb.toString(); | ||||
| 	private String getArgumentResolutionErrorMessage(String text, int index) { | ||||
| 		Class<?> paramType = getMethodParameters()[index].getParameterType(); | ||||
| 		return text + " argument " + index + " of type '" + paramType.getName() + "'"; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -197,8 +183,8 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		} | ||||
| 		catch (IllegalArgumentException ex) { | ||||
| 			assertTargetBean(getBridgedMethod(), getBean(), args); | ||||
| 			String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(message, args), ex); | ||||
| 			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(text, args), ex); | ||||
| 		} | ||||
| 		catch (InvocationTargetException ex) { | ||||
| 			// Unwrap for HandlerExceptionResolvers ... | ||||
|  | @ -213,33 +199,33 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 				throw (Exception) targetException; | ||||
| 			} | ||||
| 			else { | ||||
| 				String msg = getInvocationErrorMessage("Failed to invoke controller method", args); | ||||
| 				throw new IllegalStateException(msg, targetException); | ||||
| 				String text = getInvocationErrorMessage("Failed to invoke handler method", args); | ||||
| 				throw new IllegalStateException(text, targetException); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Assert that the target bean class is an instance of the class where the given | ||||
| 	 * method is declared. In some cases the actual controller instance at request- | ||||
| 	 * method is declared. In some cases the actual endpoint instance at request- | ||||
| 	 * processing time may be a JDK dynamic proxy (lazy initialization, prototype | ||||
| 	 * beans, and others). {@code @Controller}'s that require proxying should prefer | ||||
| 	 * beans, and others). Endpoint classes that require proxying should prefer | ||||
| 	 * class-based proxy mechanisms. | ||||
| 	 */ | ||||
| 	private void assertTargetBean(Method method, Object targetBean, Object[] args) { | ||||
| 		Class<?> methodDeclaringClass = method.getDeclaringClass(); | ||||
| 		Class<?> targetBeanClass = targetBean.getClass(); | ||||
| 		if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { | ||||
| 			String msg = "The mapped controller method class '" + methodDeclaringClass.getName() + | ||||
| 					"' is not an instance of the actual controller bean class '" + | ||||
| 					targetBeanClass.getName() + "'. If the controller requires proxying " + | ||||
| 			String text = "The mapped handler method class '" + methodDeclaringClass.getName() + | ||||
| 					"' is not an instance of the actual endpoint bean class '" + | ||||
| 					targetBeanClass.getName() + "'. If the endpoint requires proxying " + | ||||
| 					"(e.g. due to @Transactional), please use class-based proxying."; | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(msg, args)); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(text, args)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private String getInvocationErrorMessage(String message, Object[] resolvedArgs) { | ||||
| 		StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message)); | ||||
| 	private String getInvocationErrorMessage(String text, Object[] resolvedArgs) { | ||||
| 		StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text)); | ||||
| 		sb.append("Resolved arguments: \n"); | ||||
| 		for (int i = 0; i < resolvedArgs.length; i++) { | ||||
| 			sb.append("[").append(i).append("] "); | ||||
|  | @ -254,6 +240,19 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		return sb.toString(); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Adds HandlerMethod details such as the bean type and method signature to the message. | ||||
| 	 * @param text error message to append the HandlerMethod details to | ||||
| 	 */ | ||||
| 	protected String getDetailedErrorMessage(String text) { | ||||
| 		StringBuilder sb = new StringBuilder(text).append("\n"); | ||||
| 		sb.append("HandlerMethod details: \n"); | ||||
| 		sb.append("Endpoint [").append(getBeanType().getName()).append("]\n"); | ||||
| 		sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); | ||||
| 		return sb.toString(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	MethodParameter getAsyncReturnValueType(Object returnValue) { | ||||
| 		return new AsyncResultMethodParameter(returnValue); | ||||
| 	} | ||||
|  | @ -268,7 +267,7 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		public AsyncResultMethodParameter(Object returnValue) { | ||||
| 			super(-1); | ||||
| 			this.returnValue = returnValue; | ||||
| 			this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0); | ||||
| 			this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected AsyncResultMethodParameter(AsyncResultMethodParameter original) { | ||||
|  |  | |||
|  | @ -0,0 +1,68 @@ | |||
| /* | ||||
|  * 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. | ||||
|  * 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.messaging.handler.invocation; | ||||
| 
 | ||||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.messaging.Message; | ||||
| import org.springframework.messaging.MessagingException; | ||||
| 
 | ||||
| /** | ||||
|  * Common exception resulting from the invocation of | ||||
|  * {@link org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver}. | ||||
|  * | ||||
|  * @author Juergen Hoeller | ||||
|  * @since 4.3.6 | ||||
|  * @see HandlerMethodArgumentResolver | ||||
|  */ | ||||
| @SuppressWarnings("serial") | ||||
| public class MethodArgumentResolutionException extends MessagingException { | ||||
| 
 | ||||
| 	private final MethodParameter parameter; | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance providing the invalid {@code MethodParameter}. | ||||
| 	 */ | ||||
| 	public MethodArgumentResolutionException(Message<?> message, MethodParameter parameter) { | ||||
| 		super(message, getMethodParameterMessage(parameter)); | ||||
| 		this.parameter = parameter; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new instance providing the invalid {@code MethodParameter} and | ||||
| 	 * a prepared description. | ||||
| 	 */ | ||||
| 	public MethodArgumentResolutionException(Message<?> message, MethodParameter parameter, String description) { | ||||
| 		super(message, getMethodParameterMessage(parameter) + ": " + description); | ||||
| 		this.parameter = parameter; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the MethodParameter that was rejected. | ||||
| 	 */ | ||||
| 	public final MethodParameter getMethodParameter() { | ||||
| 		return this.parameter; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	private static String getMethodParameterMessage(MethodParameter parameter) { | ||||
| 		return "Could not resolve method parameter at index " + parameter.getParameterIndex() + | ||||
| 				" in " + parameter.getMethod().toGenericString(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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. | ||||
|  | @ -41,6 +41,7 @@ import org.springframework.messaging.converter.MessageConverter; | |||
| import org.springframework.messaging.handler.annotation.Payload; | ||||
| import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; | ||||
| import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; | ||||
| import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException; | ||||
| import org.springframework.messaging.support.MessageBuilder; | ||||
| import org.springframework.util.ReflectionUtils; | ||||
| import org.springframework.validation.Errors; | ||||
|  | @ -53,13 +54,14 @@ import static org.junit.Assert.*; | |||
|  */ | ||||
| public class DefaultMessageHandlerMethodFactoryTests { | ||||
| 
 | ||||
| 	private final SampleBean sample = new SampleBean(); | ||||
| 
 | ||||
| 	@Rule | ||||
| 	public final TestName name = new TestName(); | ||||
| 
 | ||||
| 	@Rule | ||||
| 	public final ExpectedException thrown = ExpectedException.none(); | ||||
| 
 | ||||
| 	private final SampleBean sample = new SampleBean(); | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void customConversion() throws Exception { | ||||
|  | @ -146,7 +148,7 @@ public class DefaultMessageHandlerMethodFactoryTests { | |||
| 		InvocableHandlerMethod invocableHandlerMethod2 = | ||||
| 				createInvocableHandlerMethod(instance, "simpleString", String.class); | ||||
| 
 | ||||
| 		thrown.expect(IllegalStateException.class); | ||||
| 		thrown.expect(MethodArgumentResolutionException.class); | ||||
| 		thrown.expectMessage("No suitable resolver for"); | ||||
| 		invocableHandlerMethod2.invoke(message); | ||||
| 	} | ||||
|  | @ -240,4 +242,5 @@ public class DefaultMessageHandlerMethodFactoryTests { | |||
| 			return Locale.getDefault(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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")); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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. | ||||
|  | @ -163,36 +163,23 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 				} | ||||
| 				catch (Exception ex) { | ||||
| 					if (logger.isDebugEnabled()) { | ||||
| 						logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); | ||||
| 						logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); | ||||
| 					} | ||||
| 					throw ex; | ||||
| 				} | ||||
| 			} | ||||
| 			if (args[i] == null) { | ||||
| 				String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); | ||||
| 				throw new IllegalStateException(msg); | ||||
| 				throw new IllegalStateException("Could not resolve method parameter at index " + | ||||
| 						parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() + | ||||
| 						": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); | ||||
| 			} | ||||
| 		} | ||||
| 		return args; | ||||
| 	} | ||||
| 
 | ||||
| 	private String getArgumentResolutionErrorMessage(String message, int index) { | ||||
| 		MethodParameter param = getMethodParameters()[index]; | ||||
| 		message += " [" + index + "] [type=" + param.getParameterType().getName() + "]"; | ||||
| 		return getDetailedErrorMessage(message); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Adds HandlerMethod details such as the controller type and method | ||||
| 	 * signature to the given error message. | ||||
| 	 * @param message error message to append the HandlerMethod details to | ||||
| 	 */ | ||||
| 	protected String getDetailedErrorMessage(String message) { | ||||
| 		StringBuilder sb = new StringBuilder(message).append("\n"); | ||||
| 		sb.append("HandlerMethod details: \n"); | ||||
| 		sb.append("Controller [").append(getBeanType().getName()).append("]\n"); | ||||
| 		sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); | ||||
| 		return sb.toString(); | ||||
| 	private String getArgumentResolutionErrorMessage(String text, int index) { | ||||
| 		Class<?> paramType = getMethodParameters()[index].getParameterType(); | ||||
| 		return text + " argument " + index + " of type '" + paramType.getName() + "'"; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -221,8 +208,8 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		} | ||||
| 		catch (IllegalArgumentException ex) { | ||||
| 			assertTargetBean(getBridgedMethod(), getBean(), args); | ||||
| 			String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(message, args), ex); | ||||
| 			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(text, args), ex); | ||||
| 		} | ||||
| 		catch (InvocationTargetException ex) { | ||||
| 			// Unwrap for HandlerExceptionResolvers ... | ||||
|  | @ -237,8 +224,8 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 				throw (Exception) targetException; | ||||
| 			} | ||||
| 			else { | ||||
| 				String msg = getInvocationErrorMessage("Failed to invoke controller method", args); | ||||
| 				throw new IllegalStateException(msg, targetException); | ||||
| 				String text = getInvocationErrorMessage("Failed to invoke handler method", args); | ||||
| 				throw new IllegalStateException(text, targetException); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | @ -254,16 +241,16 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		Class<?> methodDeclaringClass = method.getDeclaringClass(); | ||||
| 		Class<?> targetBeanClass = targetBean.getClass(); | ||||
| 		if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) { | ||||
| 			String msg = "The mapped controller method class '" + methodDeclaringClass.getName() + | ||||
| 			String text = "The mapped handler method class '" + methodDeclaringClass.getName() + | ||||
| 					"' is not an instance of the actual controller bean class '" + | ||||
| 					targetBeanClass.getName() + "'. If the controller requires proxying " + | ||||
| 					"(e.g. due to @Transactional), please use class-based proxying."; | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(msg, args)); | ||||
| 			throw new IllegalStateException(getInvocationErrorMessage(text, args)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private String getInvocationErrorMessage(String message, Object[] resolvedArgs) { | ||||
| 		StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message)); | ||||
| 	private String getInvocationErrorMessage(String text, Object[] resolvedArgs) { | ||||
| 		StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text)); | ||||
| 		sb.append("Resolved arguments: \n"); | ||||
| 		for (int i = 0; i < resolvedArgs.length; i++) { | ||||
| 			sb.append("[").append(i).append("] "); | ||||
|  | @ -278,4 +265,16 @@ public class InvocableHandlerMethod extends HandlerMethod { | |||
| 		return sb.toString(); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Adds HandlerMethod details such as the bean type and method signature to the message. | ||||
| 	 * @param text error message to append the HandlerMethod details to | ||||
| 	 */ | ||||
| 	protected String getDetailedErrorMessage(String text) { | ||||
| 		StringBuilder sb = new StringBuilder(text).append("\n"); | ||||
| 		sb.append("HandlerMethod details: \n"); | ||||
| 		sb.append("Controller [").append(getBeanType().getName()).append("]\n"); | ||||
| 		sb.append("Method [").append(getBridgedMethod().toGenericString()).append("]\n"); | ||||
| 		return sb.toString(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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. | ||||
|  | @ -93,7 +93,7 @@ public class InvocableHandlerMethodTests { | |||
| 			fail("Expected exception"); | ||||
| 		} | ||||
| 		catch (IllegalStateException ex) { | ||||
| 			assertTrue(ex.getMessage().contains("No suitable resolver for argument [0] [type=java.lang.Integer]")); | ||||
| 			assertTrue(ex.getMessage().contains("No suitable resolver for argument 0 of type 'java.lang.Integer'")); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -192,7 +192,7 @@ public class InvocableHandlerMethodTests { | |||
| 		catch (IllegalStateException actual) { | ||||
| 			assertNotNull(actual.getCause()); | ||||
| 			assertSame(expected, actual.getCause()); | ||||
| 			assertTrue(actual.getMessage().contains("Failed to invoke controller method")); | ||||
| 			assertTrue(actual.getMessage().contains("Failed to invoke handler method")); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue