1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.jms.config;
18
19 import java.io.Serializable;
20 import java.lang.reflect.Method;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.Map;
24 import javax.jms.Destination;
25 import javax.jms.InvalidDestinationException;
26 import javax.jms.JMSException;
27 import javax.jms.ObjectMessage;
28 import javax.jms.QueueSender;
29 import javax.jms.Session;
30 import javax.jms.TextMessage;
31
32 import org.hamcrest.Matchers;
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.rules.ExpectedException;
37 import org.junit.rules.TestName;
38
39 import org.springframework.beans.factory.support.StaticListableBeanFactory;
40 import org.springframework.jms.StubTextMessage;
41 import org.springframework.jms.listener.DefaultMessageListenerContainer;
42 import org.springframework.jms.listener.MessageListenerContainer;
43 import org.springframework.jms.listener.SimpleMessageListenerContainer;
44 import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
45 import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
46 import org.springframework.jms.listener.adapter.ReplyFailureException;
47 import org.springframework.jms.support.JmsHeaders;
48 import org.springframework.jms.support.JmsMessageHeaderAccessor;
49 import org.springframework.jms.support.destination.DestinationResolver;
50 import org.springframework.messaging.Message;
51 import org.springframework.messaging.MessageHeaders;
52 import org.springframework.messaging.converter.MessageConversionException;
53 import org.springframework.messaging.handler.annotation.Header;
54 import org.springframework.messaging.handler.annotation.Headers;
55 import org.springframework.messaging.handler.annotation.Payload;
56 import org.springframework.messaging.handler.annotation.SendTo;
57 import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
58 import org.springframework.messaging.handler.annotation.support.MethodArgumentTypeMismatchException;
59 import org.springframework.util.ReflectionUtils;
60 import org.springframework.validation.Errors;
61 import org.springframework.validation.Validator;
62 import org.springframework.validation.annotation.Validated;
63
64 import static org.junit.Assert.*;
65 import static org.mockito.BDDMockito.*;
66
67
68
69
70 public class MethodJmsListenerEndpointTests {
71
72 @Rule
73 public final TestName name = new TestName();
74
75 @Rule
76 public final ExpectedException thrown = ExpectedException.none();
77
78 private final DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
79
80 private final DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
81
82 private final JmsEndpointSampleBean sample = new JmsEndpointSampleBean();
83
84
85 @Before
86 public void setup() {
87 initializeFactory(factory);
88 }
89
90 @Test
91 public void createMessageListenerNoFactory() {
92 MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
93 endpoint.setBean(this);
94 endpoint.setMethod(getTestMethod());
95
96 thrown.expect(IllegalStateException.class);
97 endpoint.createMessageListener(container);
98 }
99
100 @Test
101 public void createMessageListener() {
102 MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
103 endpoint.setBean(this);
104 endpoint.setMethod(getTestMethod());
105 endpoint.setMessageHandlerMethodFactory(factory);
106
107 assertNotNull(endpoint.createMessageListener(container));
108 }
109
110 @Test
111 public void resolveMessageAndSession() throws JMSException {
112 MessagingMessageListenerAdapter listener = createDefaultInstance(javax.jms.Message.class, Session.class);
113
114 Session session = mock(Session.class);
115 listener.onMessage(createSimpleJmsTextMessage("test"), session);
116 assertDefaultListenerMethodInvocation();
117 }
118
119 @Test
120 public void resolveGenericMessage() throws JMSException {
121 MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class);
122
123 Session session = mock(Session.class);
124 listener.onMessage(createSimpleJmsTextMessage("test"), session);
125 assertDefaultListenerMethodInvocation();
126 }
127
128 @Test
129 public void resolveHeaderAndPayload() throws JMSException {
130 MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class);
131
132 Session session = mock(Session.class);
133 StubTextMessage message = createSimpleJmsTextMessage("my payload");
134 message.setIntProperty("myCounter", 55);
135 listener.onMessage(message, session);
136 assertDefaultListenerMethodInvocation();
137 }
138
139 @Test
140 public void resolveCustomHeaderNameAndPayload() throws JMSException {
141 MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class);
142
143 Session session = mock(Session.class);
144 StubTextMessage message = createSimpleJmsTextMessage("my payload");
145 message.setIntProperty("myCounter", 24);
146 listener.onMessage(message, session);
147 assertDefaultListenerMethodInvocation();
148 }
149
150 @Test
151 public void resolveHeaders() throws JMSException {
152 MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, Map.class);
153
154 Session session = mock(Session.class);
155 StubTextMessage message = createSimpleJmsTextMessage("my payload");
156 message.setIntProperty("customInt", 1234);
157 message.setJMSMessageID("abcd-1234");
158 listener.onMessage(message, session);
159 assertDefaultListenerMethodInvocation();
160 }
161
162 @Test
163 public void resolveMessageHeaders() throws JMSException {
164 MessagingMessageListenerAdapter listener = createDefaultInstance(MessageHeaders.class);
165
166 Session session = mock(Session.class);
167 StubTextMessage message = createSimpleJmsTextMessage("my payload");
168 message.setLongProperty("customLong", 4567L);
169 message.setJMSType("myMessageType");
170 listener.onMessage(message, session);
171 assertDefaultListenerMethodInvocation();
172 }
173
174 @Test
175 public void resolveJmsMessageHeaderAccessor() throws JMSException {
176 MessagingMessageListenerAdapter listener = createDefaultInstance(JmsMessageHeaderAccessor.class);
177
178 Session session = mock(Session.class);
179 StubTextMessage message = createSimpleJmsTextMessage("my payload");
180 message.setBooleanProperty("customBoolean", true);
181 message.setJMSPriority(9);
182 listener.onMessage(message, session);
183 assertDefaultListenerMethodInvocation();
184 }
185
186 @Test
187 public void resolveObjectPayload() throws JMSException {
188 MessagingMessageListenerAdapter listener = createDefaultInstance(MyBean.class);
189 MyBean myBean = new MyBean();
190 myBean.name = "myBean name";
191
192 Session session = mock(Session.class);
193 ObjectMessage message = mock(ObjectMessage.class);
194 given(message.getObject()).willReturn(myBean);
195
196 listener.onMessage(message, session);
197 assertDefaultListenerMethodInvocation();
198 }
199
200 @Test
201 public void resolveConvertedPayload() throws JMSException {
202 MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class);
203
204 Session session = mock(Session.class);
205
206 listener.onMessage(createSimpleJmsTextMessage("33"), session);
207 assertDefaultListenerMethodInvocation();
208 }
209
210 @Test
211 public void processAndReply() throws JMSException {
212 MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
213 String body = "echo text";
214 String correlationId = "link-1234";
215 Destination replyDestination = new Destination() {};
216
217 TextMessage reply = mock(TextMessage.class);
218 QueueSender queueSender = mock(QueueSender.class);
219 Session session = mock(Session.class);
220 given(session.createTextMessage(body)).willReturn(reply);
221 given(session.createProducer(replyDestination)).willReturn(queueSender);
222
223 listener.setDefaultResponseDestination(replyDestination);
224 StubTextMessage inputMessage = createSimpleJmsTextMessage(body);
225 inputMessage.setJMSCorrelationID(correlationId);
226 listener.onMessage(inputMessage, session);
227 assertDefaultListenerMethodInvocation();
228
229 verify(reply).setJMSCorrelationID(correlationId);
230 verify(queueSender).send(reply);
231 verify(queueSender).close();
232 }
233
234 @Test
235 public void processAndReplyWithSendToQueue() throws JMSException {
236 String methodName = "processAndReplyWithSendTo";
237 SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
238 MessagingMessageListenerAdapter listener = createInstance(this.factory,
239 getListenerMethod(methodName, String.class), container);
240 processAndReplyWithSendTo(listener, false);
241 assertListenerMethodInvocation(sample, methodName);
242 }
243
244 @Test
245 public void processAndReplyWithSendToTopic() throws JMSException {
246 String methodName = "processAndReplyWithSendTo";
247 SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
248 container.setPubSubDomain(true);
249 MessagingMessageListenerAdapter listener = createInstance(this.factory,
250 getListenerMethod(methodName, String.class), container);
251 processAndReplyWithSendTo(listener, true);
252 assertListenerMethodInvocation(sample, methodName);
253 }
254
255 private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener, boolean pubSubDomain) throws JMSException {
256 String body = "echo text";
257 String correlationId = "link-1234";
258 Destination replyDestination = new Destination() {};
259
260 DestinationResolver destinationResolver = mock(DestinationResolver.class);
261 TextMessage reply = mock(TextMessage.class);
262 QueueSender queueSender = mock(QueueSender.class);
263 Session session = mock(Session.class);
264
265 given(destinationResolver.resolveDestinationName(session, "replyDestination", pubSubDomain))
266 .willReturn(replyDestination);
267 given(session.createTextMessage(body)).willReturn(reply);
268 given(session.createProducer(replyDestination)).willReturn(queueSender);
269
270 listener.setDestinationResolver(destinationResolver);
271 StubTextMessage inputMessage = createSimpleJmsTextMessage(body);
272 inputMessage.setJMSCorrelationID(correlationId);
273 listener.onMessage(inputMessage, session);
274
275 verify(destinationResolver).resolveDestinationName(session, "replyDestination", pubSubDomain);
276 verify(reply).setJMSCorrelationID(correlationId);
277 verify(queueSender).send(reply);
278 verify(queueSender).close();
279 }
280
281 @Test
282 public void emptySendTo() throws JMSException {
283 MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
284
285 TextMessage reply = mock(TextMessage.class);
286 Session session = mock(Session.class);
287 given(session.createTextMessage("content")).willReturn(reply);
288
289 thrown.expect(ReplyFailureException.class);
290 thrown.expectCause(Matchers.isA(InvalidDestinationException.class));
291 listener.onMessage(createSimpleJmsTextMessage("content"), session);
292 }
293
294 @Test
295 public void invalidSendTo() {
296 thrown.expect(IllegalStateException.class);
297 thrown.expectMessage("firstDestination");
298 thrown.expectMessage("secondDestination");
299 createDefaultInstance(String.class);
300 }
301
302 @Test
303 public void validatePayloadValid() throws JMSException {
304 String methodName = "validatePayload";
305
306 DefaultMessageHandlerMethodFactory customFactory = new DefaultMessageHandlerMethodFactory();
307 customFactory.setValidator(testValidator("invalid value"));
308 initializeFactory(customFactory);
309
310 Method method = getListenerMethod(methodName, String.class);
311 MessagingMessageListenerAdapter listener = createInstance(customFactory, method);
312 Session session = mock(Session.class);
313 listener.onMessage(createSimpleJmsTextMessage("test"), session);
314 assertListenerMethodInvocation(sample, methodName);
315 }
316
317 @Test
318 public void validatePayloadInvalid() throws JMSException {
319 DefaultMessageHandlerMethodFactory customFactory = new DefaultMessageHandlerMethodFactory();
320 customFactory.setValidator(testValidator("invalid value"));
321
322 Method method = getListenerMethod("validatePayload", String.class);
323 MessagingMessageListenerAdapter listener = createInstance(customFactory, method);
324 Session session = mock(Session.class);
325
326 thrown.expect(ListenerExecutionFailedException.class);
327 listener.onMessage(createSimpleJmsTextMessage("invalid value"), session);
328
329 }
330
331
332
333 @Test
334 public void invalidPayloadType() throws JMSException {
335 MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class);
336 Session session = mock(Session.class);
337
338 thrown.expect(ListenerExecutionFailedException.class);
339 thrown.expectCause(Matchers.isA(MessageConversionException.class));
340 thrown.expectMessage(getDefaultListenerMethod(Integer.class).toGenericString());
341 listener.onMessage(createSimpleJmsTextMessage("test"), session);
342 }
343
344 @Test
345 public void invalidMessagePayloadType() throws JMSException {
346 MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class);
347 Session session = mock(Session.class);
348
349 thrown.expect(ListenerExecutionFailedException.class);
350 thrown.expectCause(Matchers.isA(MethodArgumentTypeMismatchException.class));
351 listener.onMessage(createSimpleJmsTextMessage("test"), session);
352 }
353
354 private MessagingMessageListenerAdapter createInstance(
355 DefaultMessageHandlerMethodFactory factory, Method method, MessageListenerContainer container) {
356 MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
357 endpoint.setBean(sample);
358 endpoint.setMethod(method);
359 endpoint.setMessageHandlerMethodFactory(factory);
360 return endpoint.createMessageListener(container);
361 }
362
363 private MessagingMessageListenerAdapter createInstance(
364 DefaultMessageHandlerMethodFactory factory, Method method) {
365 return createInstance(factory, method, new SimpleMessageListenerContainer());
366 }
367
368 private MessagingMessageListenerAdapter createDefaultInstance(Class<?>... parameterTypes) {
369 return createInstance(this.factory, getDefaultListenerMethod(parameterTypes));
370 }
371
372 private StubTextMessage createSimpleJmsTextMessage(String body) {
373 return new StubTextMessage(body);
374 }
375
376 private Method getListenerMethod(String methodName, Class<?>... parameterTypes) {
377 Method method = ReflectionUtils.findMethod(JmsEndpointSampleBean.class, methodName, parameterTypes);
378 assertNotNull("no method found with name " + methodName + " and parameters " + Arrays.toString(parameterTypes));
379 return method;
380 }
381
382 private Method getDefaultListenerMethod(Class<?>... parameterTypes) {
383 return getListenerMethod(name.getMethodName(), parameterTypes);
384 }
385
386 private void assertDefaultListenerMethodInvocation() {
387 assertListenerMethodInvocation(sample, name.getMethodName());
388 }
389
390 private void assertListenerMethodInvocation(JmsEndpointSampleBean bean, String methodName) {
391 assertTrue("Method " + methodName + " should have been invoked", bean.invocations.get(methodName));
392 }
393
394 private void initializeFactory(DefaultMessageHandlerMethodFactory factory) {
395 factory.setBeanFactory(new StaticListableBeanFactory());
396 factory.afterPropertiesSet();
397 }
398
399 private Validator testValidator(final String invalidValue) {
400 return new Validator() {
401 @Override
402 public boolean supports(Class<?> clazz) {
403 return String.class.isAssignableFrom(clazz);
404 }
405 @Override
406 public void validate(Object target, Errors errors) {
407 String value = (String) target;
408 if (invalidValue.equals(value)) {
409 errors.reject("not a valid value");
410 }
411 }
412 };
413 }
414
415 private Method getTestMethod() {
416 return ReflectionUtils.findMethod(MethodJmsListenerEndpointTests.class, name.getMethodName());
417 }
418
419
420 static class JmsEndpointSampleBean {
421
422 private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
423
424 public void resolveMessageAndSession(javax.jms.Message message, Session session) {
425 invocations.put("resolveMessageAndSession", true);
426 assertNotNull("Message not injected", message);
427 assertNotNull("Session not injected", session);
428 }
429
430 public void resolveGenericMessage(Message<String> message) {
431 invocations.put("resolveGenericMessage", true);
432 assertNotNull("Generic message not injected", message);
433 assertEquals("Wrong message payload", "test", message.getPayload());
434 }
435
436 public void resolveHeaderAndPayload(@Payload String content, @Header int myCounter) {
437 invocations.put("resolveHeaderAndPayload", true);
438 assertEquals("Wrong @Payload resolution", "my payload", content);
439 assertEquals("Wrong @Header resolution", 55, myCounter);
440 }
441
442 public void resolveCustomHeaderNameAndPayload(@Payload String content, @Header("myCounter") int counter) {
443 invocations.put("resolveCustomHeaderNameAndPayload", true);
444 assertEquals("Wrong @Payload resolution", "my payload", content);
445 assertEquals("Wrong @Header resolution", 24, counter);
446 }
447
448 public void resolveHeaders(String content, @Headers Map<String, Object> headers) {
449 invocations.put("resolveHeaders", true);
450 assertEquals("Wrong payload resolution", "my payload", content);
451 assertNotNull("headers not injected", headers);
452 assertEquals("Missing JMS message id header", "abcd-1234", headers.get(JmsHeaders.MESSAGE_ID));
453 assertEquals("Missing custom header", 1234, headers.get("customInt"));
454 }
455
456 public void resolveMessageHeaders(MessageHeaders headers) {
457 invocations.put("resolveMessageHeaders", true);
458 assertNotNull("MessageHeaders not injected", headers);
459 assertEquals("Missing JMS message type header", "myMessageType", headers.get(JmsHeaders.TYPE));
460 assertEquals("Missing custom header", 4567L, (long) headers.get("customLong"), 0.0);
461 }
462
463 public void resolveJmsMessageHeaderAccessor(JmsMessageHeaderAccessor headers) {
464 invocations.put("resolveJmsMessageHeaderAccessor", true);
465 assertNotNull("MessageHeaders not injected", headers);
466 assertEquals("Missing JMS message priority header", Integer.valueOf(9), headers.getPriority());
467 assertEquals("Missing custom header", true, headers.getHeader("customBoolean"));
468 }
469
470 public void resolveObjectPayload(MyBean bean) {
471 invocations.put("resolveObjectPayload", true);
472 assertNotNull("Object payload not injected", bean);
473 assertEquals("Wrong content for payload", "myBean name", bean.name);
474 }
475
476 public void resolveConvertedPayload(Integer counter) {
477 invocations.put("resolveConvertedPayload", true);
478 assertNotNull("Payload not injected", counter);
479 assertEquals("Wrong content for payload", Integer.valueOf(33), counter);
480 }
481
482 public String processAndReply(@Payload String content) {
483 invocations.put("processAndReply", true);
484 return content;
485 }
486
487 @SendTo("replyDestination")
488 public String processAndReplyWithSendTo(String content) {
489 invocations.put("processAndReplyWithSendTo", true);
490 return content;
491 }
492
493 @SendTo("")
494 public String emptySendTo(String content) {
495 invocations.put("emptySendTo", true);
496 return content;
497 }
498
499 @SendTo({"firstDestination", "secondDestination"})
500 public String invalidSendTo(String content) {
501 invocations.put("invalidSendTo", true);
502 return content;
503 }
504
505 public void validatePayload(@Validated String payload) {
506 invocations.put("validatePayload", true);
507 }
508
509 public void invalidPayloadType(@Payload Integer payload) {
510 throw new IllegalStateException("Should never be called.");
511 }
512
513 public void invalidMessagePayloadType(Message<Integer> message) {
514 throw new IllegalStateException("Should never be called.");
515 }
516
517 }
518
519
520 @SuppressWarnings("serial")
521 static class MyBean implements Serializable {
522 private String name;
523
524 }
525
526 }