View Javadoc
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.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   * @author Stephane Nicoll
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); // test is a valid value
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); // test is an invalid value
328 
329 	}
330 
331 	// failure scenario
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()); // ref to method
341 		listener.onMessage(createSimpleJmsTextMessage("test"), session); // test is not a valid integer
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);  // Message<String> as Message<Integer>
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 }