Browse Source

Introduce base exception class for arg resolution

Issue: SPR-11584
pull/497/merge
Rossen Stoyanchev 12 years ago
parent
commit
2c1d5efbb0
  1. 68
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/AbstractMethodArgumentResolutionException.java
  2. 36
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolver.java
  3. 39
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java
  4. 12
      spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java
  5. 63
      spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/MessageMethodArgumentResolverTests.java

68
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();
}
}

36
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import java.lang.reflect.Type;
/** /**
* A {@link HandlerMethodArgumentResolver} for {@link Message} parameters. Validates * A {@link HandlerMethodArgumentResolver} for {@link Message} parameters. Validates
* that the generic type of the payload matches with the message value. * that the generic type of the payload matches with the message value.
@ -39,28 +41,30 @@ public class MessageMethodArgumentResolver implements HandlerMethodArgumentResol
@Override @Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception { public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
// Validate the message type is assignable
if (!parameter.getParameterType().isAssignableFrom(message.getClass())) { Class<?> paramType = parameter.getParameterType();
throw new MethodArgumentTypeMismatchException(message,
"Could not resolve Message parameter: invalid message type:" if (!paramType.isAssignableFrom(message.getClass())) {
+ "expected [" + message.getClass().getName() + "] but got [" throw new MethodArgumentTypeMismatchException(message, parameter,
+ parameter.getParameterType().getName() + "]"); "The actual message type [" + message.getClass().getName() + "] " +
"does not match the expected type [" + paramType.getName() + "]");
} }
// validate that the payload type matches Class<?> expectedPayloadType = getPayloadType(parameter);
Class<?> effectivePayloadType = getPayloadType(parameter); Object payload = message.getPayload();
if (effectivePayloadType != null && !effectivePayloadType.isInstance(message.getPayload())) {
throw new MethodArgumentTypeMismatchException(message, if (expectedPayloadType != null && !expectedPayloadType.isInstance(payload)) {
"Could not resolve Message parameter: invalid payload type: " throw new MethodArgumentTypeMismatchException(message, parameter,
+ "expected [" + effectivePayloadType.getName() + "] but got [" "The expected Message<?> payload type [" + expectedPayloadType.getClass().getName() +
+ message.getPayload().getClass().getName() + "]"); "] does not match the actual payload type [" + payload.getClass().getName() + "]");
} }
return message; return message;
} }
private Class<?> getPayloadType(MethodParameter parameter) { private Class<?> getPayloadType(MethodParameter parameter) {
ResolvableType resolvableType = ResolvableType Type genericParamType = parameter.getGenericParameterType();
.forType(parameter.getGenericParameterType()).as(Message.class); ResolvableType resolvableType = ResolvableType.forType(genericParamType).as(Message.class);
return resolvableType.getGeneric(0).resolve(Object.class); return resolvableType.getGeneric(0).resolve(Object.class);
} }

39
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.core.MethodParameter;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError; import org.springframework.validation.ObjectError;
/** /**
* Exception to be thrown when a method argument is not valid. For instance, this * Exception to be thrown when a method argument fails validation perhaps as a
* can be issued if a validation on a method parameter annotated with * result of {@code @Valid} style validation, or perhaps because it is required.
* {@code @Valid} fails.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Rossen Stoyanchev
* @since 4.0.1 * @since 4.0.1
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class MethodArgumentNotValidException extends MessagingException { public class MethodArgumentNotValidException extends AbstractMethodArgumentResolutionException {
private final MethodParameter parameter;
private final BindingResult bindingResult; private final BindingResult bindingResult;
@ -41,7 +38,6 @@ public class MethodArgumentNotValidException extends MessagingException {
/** /**
* Create a new instance with the invalid {@code MethodParameter}. * Create a new instance with the invalid {@code MethodParameter}.
*/ */
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter) { public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter) {
this(message, parameter, null); this(message, parameter, null);
} }
@ -53,19 +49,13 @@ public class MethodArgumentNotValidException extends MessagingException {
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter, public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter,
BindingResult bindingResult) { BindingResult bindingResult) {
super(message, generateMessage(parameter, bindingResult)); super(message, parameter, getMethodParamMessage(parameter) +
this.parameter = parameter; getValidationErrorMessage(parameter, bindingResult));
this.bindingResult = 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}. * 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) { private static String getValidationErrorMessage(MethodParameter parameter, BindingResult bindingResult) {
StringBuilder sb = new StringBuilder("Invalid parameter at index ")
.append(parameter.getParameterIndex()).append(" in method: ")
.append(parameter.getMethod().toGenericString());
if (bindingResult != null) { if (bindingResult != null) {
StringBuilder sb = new StringBuilder();
sb.append(", with ").append(bindingResult.getErrorCount()).append(" error(s): "); sb.append(", with ").append(bindingResult.getErrorCount()).append(" error(s): ");
for (ObjectError error : bindingResult.getAllErrors()) { for (ObjectError error : bindingResult.getAllErrors()) {
sb.append("[").append(error).append("] "); sb.append("[").append(error).append("] ");
} }
return sb.toString();
}
else {
return "";
} }
return sb.toString();
} }
} }

12
spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentTypeMismatchException.java

@ -16,6 +16,7 @@
package org.springframework.messaging.handler.annotation.support; package org.springframework.messaging.handler.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessagingException;
@ -26,9 +27,14 @@ import org.springframework.messaging.MessagingException;
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 4.0.3 * @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);
} }
} }

63
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; import org.springframework.messaging.support.MessageBuilder;
/** /**
* Unit tests for
* {@link org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
@ -45,101 +47,102 @@ public class MessageMethodArgumentResolverTests {
private Method method; private Method method;
@Before @Before
public void setup() throws Exception { 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); Message.class, Message.class, Message.class, Message.class, ErrorMessage.class);
} }
@Test @Test
public void resolveAnyPayloadType() throws Exception { public void resolveAnyPayloadType() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build(); Message<String> 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolvePayloadTypeExactType() throws Exception { public void resolvePayloadTypeExactType() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build(); Message<Integer> 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolvePayloadTypeSubClass() throws Exception { public void resolvePayloadTypeSubClass() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build(); Message<Integer> 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolveInvalidPayloadType() throws Exception { public void resolveInvalidPayloadType() throws Exception {
Message<String> message = MessageBuilder.withPayload("test").build(); Message<String> 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.expect(MethodArgumentTypeMismatchException.class);
thrown.expectMessage(Integer.class.getName()); thrown.expectMessage(Integer.class.getName());
thrown.expectMessage(String.class.getName()); thrown.expectMessage(String.class.getName());
resolver.resolveArgument(parameter, message); this.resolver.resolveArgument(parameter, message);
} }
@Test @Test
public void resolveUpperBoundPayloadType() throws Exception { public void resolveUpperBoundPayloadType() throws Exception {
Message<Integer> message = MessageBuilder.withPayload(123).build(); Message<Integer> 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolveOutOfBoundPayloadType() throws Exception { public void resolveOutOfBoundPayloadType() throws Exception {
Message<Locale> message = MessageBuilder.withPayload(Locale.getDefault()).build(); Message<Locale> 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.expect(MethodArgumentTypeMismatchException.class);
thrown.expectMessage(Number.class.getName()); thrown.expectMessage(Number.class.getName());
thrown.expectMessage(Locale.class.getName()); thrown.expectMessage(Locale.class.getName());
resolver.resolveArgument(parameter, message); this.resolver.resolveArgument(parameter, message);
} }
@Test @Test
public void resolveMessageSubTypeExactMatch() throws Exception { public void resolveMessageSubTypeExactMatch() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolveMessageSubTypeSubClass() throws Exception { public void resolveMessageSubTypeSubClass() throws Exception {
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException()); 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)); assertTrue("Parameter '" + parameter + "' should be supported", this.resolver.supportsParameter(parameter));
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@Test @Test
public void resolveWrongMessageType() throws Exception { public void resolveWrongMessageType() throws Exception {
Message<? extends Throwable> message = new GenericMessage<Throwable>( Message<? extends Throwable> message = new GenericMessage<Throwable>(new UnsupportedOperationException());
new UnsupportedOperationException()); MethodParameter parameter = new MethodParameter(this.method, 4);
MethodParameter parameter = new MethodParameter(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.expect(MethodArgumentTypeMismatchException.class);
thrown.expectMessage(ErrorMessage.class.getName()); thrown.expectMessage(ErrorMessage.class.getName());
thrown.expectMessage(GenericMessage.class.getName()); thrown.expectMessage(GenericMessage.class.getName());
assertSame(message, resolver.resolveArgument(parameter, message)); assertSame(message, this.resolver.resolveArgument(parameter, message));
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")

Loading…
Cancel
Save