diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractMethodArgumentResolutionException.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractMethodArgumentResolutionException.java new file mode 100644 index 00000000000..30521ddee20 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractMethodArgumentResolutionException.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2014 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.annotation.support; + +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; + +/** + * Base class for exceptions resulting from the invocation of + * {@link org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver}. + * + * @author Rossen Stoyanchev + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public abstract class AbstractMethodArgumentResolutionException extends MessagingException { + + private final MethodParameter parameter; + + + /** + * Create a new instance providing the invalid {@code MethodParameter}. + */ + protected AbstractMethodArgumentResolutionException(Message message, MethodParameter parameter) { + this(message, parameter, getMethodParamMessage(parameter)); + } + + /** + * Create a new instance providing the invalid {@code MethodParameter} and + * a prepared description. Sub-classes should prepend the description with + * the help of {@link #getMethodParamMessage(org.springframework.core.MethodParameter)}. + */ + protected AbstractMethodArgumentResolutionException(Message message, + MethodParameter parameter, String description) { + + super(message, description); + this.parameter = parameter; + } + + /** + * Return the MethodParameter that was rejected. + */ + public MethodParameter getMethodParameter() { + return this.parameter; + } + + 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()).toString(); + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java index a190626ee1a..c0c2bf9e63f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -21,6 +21,8 @@ import org.springframework.core.ResolvableType; import org.springframework.messaging.Message; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; +import java.lang.reflect.Type; + /** * A {@link HandlerMethodArgumentResolver} for {@link Message} parameters. Validates * that the generic type of the payload matches with the message value. @@ -39,28 +41,30 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol @Override public Object resolveArgument(MethodParameter parameter, Message message) throws Exception { - // Validate the message type is assignable - if (!parameter.getParameterType().isAssignableFrom(message.getClass())) { - throw new MethodArgumentTypeMismatchException(message, - "Could not resolve Message parameter: invalid message type:" - + "expected [" + message.getClass().getName() + "] but got [" - + parameter.getParameterType().getName() + "]"); + + Class paramType = parameter.getParameterType(); + + if (!paramType.isAssignableFrom(message.getClass())) { + throw new MethodArgumentTypeMismatchException(message, parameter, + "The actual message type [" + message.getClass().getName() + "] " + + "does not match the expected type [" + paramType.getName() + "]"); } - // validate that the payload type matches - Class effectivePayloadType = getPayloadType(parameter); - if (effectivePayloadType != null && !effectivePayloadType.isInstance(message.getPayload())) { - throw new MethodArgumentTypeMismatchException(message, - "Could not resolve Message parameter: invalid payload type: " - + "expected [" + effectivePayloadType.getName() + "] but got [" - + message.getPayload().getClass().getName() + "]"); + Class expectedPayloadType = getPayloadType(parameter); + Object payload = message.getPayload(); + + if (expectedPayloadType != null && !expectedPayloadType.isInstance(payload)) { + throw new MethodArgumentTypeMismatchException(message, parameter, + "The expected Message payload type [" + expectedPayloadType.getClass().getName() + + "] does not match the actual payload type [" + payload.getClass().getName() + "]"); } + return message; } private Class getPayloadType(MethodParameter parameter) { - ResolvableType resolvableType = ResolvableType - .forType(parameter.getGenericParameterType()).as(Message.class); + Type genericParamType = parameter.getGenericParameterType(); + ResolvableType resolvableType = ResolvableType.forType(genericParamType).as(Message.class); return resolvableType.getGeneric(0).resolve(Object.class); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java index a235f08ef61..eaff0ab484c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java @@ -18,22 +18,19 @@ 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.validation.BindingResult; import org.springframework.validation.ObjectError; /** - * Exception to be thrown when a method argument is not valid. For instance, this - * can be issued if a validation on a method parameter annotated with - * {@code @Valid} fails. + * Exception to be thrown when a method argument fails validation perhaps as a + * result of {@code @Valid} style validation, or perhaps because it is required. * * @author Brian Clozel + * @author Rossen Stoyanchev * @since 4.0.1 */ @SuppressWarnings("serial") -public class MethodArgumentNotValidException extends MessagingException { - - private final MethodParameter parameter; +public class MethodArgumentNotValidException extends AbstractMethodArgumentResolutionException { private final BindingResult bindingResult; @@ -41,7 +38,6 @@ public class MethodArgumentNotValidException extends MessagingException { /** * Create a new instance with the invalid {@code MethodParameter}. */ - public MethodArgumentNotValidException(Message message, MethodParameter parameter) { this(message, parameter, null); } @@ -53,19 +49,13 @@ public class MethodArgumentNotValidException extends MessagingException { public MethodArgumentNotValidException(Message message, MethodParameter parameter, BindingResult bindingResult) { - super(message, generateMessage(parameter, bindingResult)); - this.parameter = parameter; + super(message, parameter, getMethodParamMessage(parameter) + + getValidationErrorMessage(parameter, bindingResult)); + this.bindingResult = bindingResult; } - /** - * Return the MethodParameter that was rejected. - */ - public MethodParameter getMethodParameter() { - return this.parameter; - } - /** * Return the BindingResult if the failure is validation-related or {@code null}. */ @@ -74,21 +64,18 @@ public class MethodArgumentNotValidException extends MessagingException { } - private static String generateMessage(MethodParameter parameter, BindingResult bindingResult) { - - StringBuilder sb = new StringBuilder("Invalid parameter at index ") - .append(parameter.getParameterIndex()).append(" in method: ") - .append(parameter.getMethod().toGenericString()); - - + private static String getValidationErrorMessage(MethodParameter parameter, 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 ""; } - - return sb.toString(); } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java index 3a4392003ae..66fc95add39 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java @@ -16,6 +16,7 @@ package org.springframework.messaging.handler.annotation.support; +import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; @@ -26,9 +27,14 @@ import org.springframework.messaging.MessagingException; * @author Stephane Nicoll * @since 4.0.3 */ -public class MethodArgumentTypeMismatchException extends MessagingException { +@SuppressWarnings("serial") +public class MethodArgumentTypeMismatchException extends AbstractMethodArgumentResolutionException { - public MethodArgumentTypeMismatchException(Message message, String description) { - super(message, description); + + /** + * Create a new instance with the invalid {@code MethodParameter}. + */ + public MethodArgumentTypeMismatchException(Message message, MethodParameter parameter, String description) { + super(message, parameter, getMethodParamMessage(parameter) + description); } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java index 6f4addb506b..c4e79eedd99 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java @@ -33,6 +33,8 @@ import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; /** + * Unit tests for + * {@link org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver}. * * @author Stephane Nicoll */ @@ -45,101 +47,102 @@ public class MessageMethodArgumentResolverTests { private Method method; + @Before public void setup() throws Exception { - method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handleMessage", + this.method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handleMessage", Message.class, Message.class, Message.class, Message.class, ErrorMessage.class); } + @Test public void resolveAnyPayloadType() throws Exception { Message message = MessageBuilder.withPayload("test").build(); - MethodParameter parameter = new MethodParameter(method, 0); + MethodParameter parameter = new MethodParameter(this.method, 0); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolvePayloadTypeExactType() throws Exception { Message message = MessageBuilder.withPayload(123).build(); - MethodParameter parameter = new MethodParameter(method, 1); + MethodParameter parameter = new MethodParameter(this.method, 1); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolvePayloadTypeSubClass() throws Exception { Message message = MessageBuilder.withPayload(123).build(); - MethodParameter parameter = new MethodParameter(method, 2); + MethodParameter parameter = new MethodParameter(this.method, 2); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolveInvalidPayloadType() throws Exception { Message message = MessageBuilder.withPayload("test").build(); - MethodParameter parameter = new MethodParameter(method, 1); + MethodParameter parameter = new MethodParameter(this.method, 1); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(Integer.class.getName()); thrown.expectMessage(String.class.getName()); - resolver.resolveArgument(parameter, message); + this.resolver.resolveArgument(parameter, message); } @Test public void resolveUpperBoundPayloadType() throws Exception { Message message = MessageBuilder.withPayload(123).build(); - MethodParameter parameter = new MethodParameter(method, 3); + MethodParameter parameter = new MethodParameter(this.method, 3); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolveOutOfBoundPayloadType() throws Exception { Message message = MessageBuilder.withPayload(Locale.getDefault()).build(); - MethodParameter parameter = new MethodParameter(method, 3); + MethodParameter parameter = new MethodParameter(this.method, 3); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(Number.class.getName()); thrown.expectMessage(Locale.class.getName()); - resolver.resolveArgument(parameter, message); + this.resolver.resolveArgument(parameter, message); } @Test public void resolveMessageSubTypeExactMatch() throws Exception { ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); - MethodParameter parameter = new MethodParameter(method, 4); + MethodParameter parameter = new MethodParameter(this.method, 4); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolveMessageSubTypeSubClass() throws Exception { ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); - MethodParameter parameter = new MethodParameter(method, 0); + MethodParameter parameter = new MethodParameter(this.method, 0); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @Test public void resolveWrongMessageType() throws Exception { - Message message = new GenericMessage( - new UnsupportedOperationException()); - MethodParameter parameter = new MethodParameter(method, 4); + Message message = new GenericMessage(new UnsupportedOperationException()); + MethodParameter parameter = new MethodParameter(this.method, 4); - assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter)); + assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter)); thrown.expect(MethodArgumentTypeMismatchException.class); thrown.expectMessage(ErrorMessage.class.getName()); thrown.expectMessage(GenericMessage.class.getName()); - assertSame(message, resolver.resolveArgument(parameter, message)); + assertSame(message, this.resolver.resolveArgument(parameter, message)); } @SuppressWarnings("unused")