Skip to content

Commit 2aee0d8

Browse files
snicollrstoyanchev
authored andcommitted
Improve MessageMethodArgumentResolver
This commit validates that the payload type of the message is assignable to the one declared in the method signature. If that is not the case, a meaningful exception message is thrown with the types mismatch. Prior to this commit, only the Message interface could be defined in the method signature: it is now possible to define a sub-class of Message if necessary which will match as long as the Message parameter is assignable to that type. Issue: SPR-11584
1 parent bbf101e commit 2aee0d8

File tree

3 files changed

+215
-2
lines changed

3 files changed

+215
-2
lines changed

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,51 @@
1717
package org.springframework.messaging.handler.annotation.support;
1818

1919
import org.springframework.core.MethodParameter;
20+
import org.springframework.core.ResolvableType;
2021
import org.springframework.messaging.Message;
2122
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
2223

2324
/**
24-
* A {@link HandlerMethodArgumentResolver} for {@link Message} parameters.
25+
* A {@link HandlerMethodArgumentResolver} for {@link Message} parameters. Validates
26+
* that the generic type of the payload matches with the message value.
2527
*
2628
* @author Rossen Stoyanchev
29+
* @author Stephane Nicoll
2730
* @since 4.0
2831
*/
2932
public class MessageMethodArgumentResolver implements HandlerMethodArgumentResolver {
3033

3134

3235
@Override
3336
public boolean supportsParameter(MethodParameter parameter) {
34-
return parameter.getParameterType().equals(Message.class);
37+
return Message.class.isAssignableFrom(parameter.getParameterType());
3538
}
3639

3740
@Override
3841
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
42+
// Validate the message type is assignable
43+
if (!parameter.getParameterType().isAssignableFrom(message.getClass())) {
44+
throw new MethodArgumentTypeMismatchException(message,
45+
"Could not resolve Message parameter: invalid message type:"
46+
+ "expected [" + message.getClass().getName() + "] but got ["
47+
+ parameter.getParameterType().getName() + "]");
48+
}
49+
50+
// validate that the payload type matches
51+
Class<?> effectivePayloadType = getPayloadType(parameter);
52+
if (effectivePayloadType != null && !effectivePayloadType.isInstance(message.getPayload())) {
53+
throw new MethodArgumentTypeMismatchException(message,
54+
"Could not resolve Message parameter: invalid payload type: "
55+
+ "expected [" + effectivePayloadType.getName() + "] but got ["
56+
+ message.getPayload().getClass().getName() + "]");
57+
}
3958
return message;
4059
}
4160

61+
private Class<?> getPayloadType(MethodParameter parameter) {
62+
ResolvableType resolvableType = ResolvableType
63+
.forType(parameter.getGenericParameterType()).as(Message.class);
64+
return resolvableType.getGeneric(0).resolve(Object.class);
65+
}
66+
4267
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.handler.annotation.support;
18+
19+
import org.springframework.messaging.Message;
20+
import org.springframework.messaging.MessagingException;
21+
22+
/**
23+
* Exception that indicates that a method argument has not the
24+
* expected type.
25+
*
26+
* @author Stephane Nicoll
27+
* @since 4.0.3
28+
*/
29+
public class MethodArgumentTypeMismatchException extends MessagingException {
30+
31+
public MethodArgumentTypeMismatchException(Message<?> message, String description) {
32+
super(message, description);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.handler.annotation.support;
18+
19+
import static org.junit.Assert.*;
20+
21+
import java.lang.reflect.Method;
22+
import java.util.Locale;
23+
24+
import org.junit.Before;
25+
import org.junit.Rule;
26+
import org.junit.Test;
27+
import org.junit.rules.ExpectedException;
28+
29+
import org.springframework.core.MethodParameter;
30+
import org.springframework.messaging.Message;
31+
import org.springframework.messaging.support.ErrorMessage;
32+
import org.springframework.messaging.support.GenericMessage;
33+
import org.springframework.messaging.support.MessageBuilder;
34+
35+
/**
36+
*
37+
* @author Stephane Nicoll
38+
*/
39+
public class MessageMethodArgumentResolverTests {
40+
41+
@Rule
42+
public final ExpectedException thrown = ExpectedException.none();
43+
44+
private final MessageMethodArgumentResolver resolver = new MessageMethodArgumentResolver();
45+
46+
private Method method;
47+
48+
@Before
49+
public void setup() throws Exception {
50+
method = MessageMethodArgumentResolverTests.class.getDeclaredMethod("handleMessage",
51+
Message.class, Message.class, Message.class, Message.class, ErrorMessage.class);
52+
}
53+
54+
@Test
55+
public void resolveAnyPayloadType() throws Exception {
56+
Message<String> message = MessageBuilder.withPayload("test").build();
57+
MethodParameter parameter = new MethodParameter(method, 0);
58+
59+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
60+
assertSame(message, resolver.resolveArgument(parameter, message));
61+
}
62+
63+
@Test
64+
public void resolvePayloadTypeExactType() throws Exception {
65+
Message<Integer> message = MessageBuilder.withPayload(123).build();
66+
MethodParameter parameter = new MethodParameter(method, 1);
67+
68+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
69+
assertSame(message, resolver.resolveArgument(parameter, message));
70+
}
71+
72+
@Test
73+
public void resolvePayloadTypeSubClass() throws Exception {
74+
Message<Integer> message = MessageBuilder.withPayload(123).build();
75+
MethodParameter parameter = new MethodParameter(method, 2);
76+
77+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
78+
assertSame(message, resolver.resolveArgument(parameter, message));
79+
}
80+
81+
@Test
82+
public void resolveInvalidPayloadType() throws Exception {
83+
Message<String> message = MessageBuilder.withPayload("test").build();
84+
MethodParameter parameter = new MethodParameter(method, 1);
85+
86+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
87+
thrown.expect(MethodArgumentTypeMismatchException.class);
88+
thrown.expectMessage(Integer.class.getName());
89+
thrown.expectMessage(String.class.getName());
90+
resolver.resolveArgument(parameter, message);
91+
}
92+
93+
@Test
94+
public void resolveUpperBoundPayloadType() throws Exception {
95+
Message<Integer> message = MessageBuilder.withPayload(123).build();
96+
MethodParameter parameter = new MethodParameter(method, 3);
97+
98+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
99+
assertSame(message, resolver.resolveArgument(parameter, message));
100+
}
101+
102+
@Test
103+
public void resolveOutOfBoundPayloadType() throws Exception {
104+
Message<Locale> message = MessageBuilder.withPayload(Locale.getDefault()).build();
105+
MethodParameter parameter = new MethodParameter(method, 3);
106+
107+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
108+
thrown.expect(MethodArgumentTypeMismatchException.class);
109+
thrown.expectMessage(Number.class.getName());
110+
thrown.expectMessage(Locale.class.getName());
111+
resolver.resolveArgument(parameter, message);
112+
}
113+
114+
@Test
115+
public void resolveMessageSubTypeExactMatch() throws Exception {
116+
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
117+
MethodParameter parameter = new MethodParameter(method, 4);
118+
119+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
120+
assertSame(message, resolver.resolveArgument(parameter, message));
121+
}
122+
123+
@Test
124+
public void resolveMessageSubTypeSubClass() throws Exception {
125+
ErrorMessage message = new ErrorMessage(new UnsupportedOperationException());
126+
MethodParameter parameter = new MethodParameter(method, 0);
127+
128+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
129+
assertSame(message, resolver.resolveArgument(parameter, message));
130+
}
131+
132+
@Test
133+
public void resolveWrongMessageType() throws Exception {
134+
Message<? extends Throwable> message = new GenericMessage<Throwable>(
135+
new UnsupportedOperationException());
136+
MethodParameter parameter = new MethodParameter(method, 4);
137+
138+
assertTrue("Parameter '" + parameter + "' should be supported", resolver.supportsParameter(parameter));
139+
thrown.expect(MethodArgumentTypeMismatchException.class);
140+
thrown.expectMessage(ErrorMessage.class.getName());
141+
thrown.expectMessage(GenericMessage.class.getName());
142+
assertSame(message, resolver.resolveArgument(parameter, message));
143+
}
144+
145+
@SuppressWarnings("unused")
146+
private void handleMessage(
147+
Message<?> wildcardPayload,
148+
Message<Integer> integerPayload,
149+
Message<Number> numberPayload,
150+
Message<? extends Number> anyNumberPayload,
151+
ErrorMessage subClass) {
152+
}
153+
154+
}

0 commit comments

Comments
 (0)