Skip to content

Commit bded025

Browse files
committed
@sendto support for jms listener endpoints
This commit replaces the "responseDestination" attribute on the JmsListener annotation by a support of the standard SendTo annotation. Issue: SPR-11707
1 parent 4b0aba6 commit bded025

File tree

9 files changed

+153
-45
lines changed

9 files changed

+153
-45
lines changed

spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
*
6262
* <p>Annotated method may have a non {@code void} return type. When they do, the result of the
6363
* method invocation is sent as a JMS reply to the destination defined by either the
64-
* {@code JMSReplyTO} header of the incoming message or the value of {@link #responseDestination()}.
64+
* {@code JMSReplyTO} header of the incoming message. When this value is not set, a default
65+
* destination can be provided by adding @{@link org.springframework.messaging.handler.annotation.SendTo
66+
* SendTo} to the method declaration.
6567
*
6668
* @author Stephane Nicoll
6769
* @since 4.1
@@ -111,14 +113,4 @@
111113
*/
112114
String selector() default "";
113115

114-
/**
115-
* The name of the default response destination to send response messages to.
116-
* <p>This will be applied in case of a request message that does not carry
117-
* a "JMSReplyTo" field. The type of this destination will be determined
118-
* by the listener-container's "destination-type" attribute.
119-
* <p>Note: This only applies to a listener method with a return value,
120-
* for which each result object will be converted into a response message.
121-
*/
122-
String responseDestination() default "";
123-
124116
}

spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,6 @@ protected void processJmsListener(JmsListener jmsListener, Method method, Object
182182
if (StringUtils.hasText(jmsListener.subscription())) {
183183
endpoint.setSubscription(jmsListener.subscription());
184184
}
185-
if (StringUtils.hasText(jmsListener.responseDestination())) {
186-
endpoint.setResponseDestination(jmsListener.responseDestination());
187-
}
188185

189186
JmsListenerContainerFactory<?> factory = null;
190187
String containerFactoryBeanName = jmsListener.containerFactory();

spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package org.springframework.jms.config;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Arrays;
2021

22+
import org.springframework.core.annotation.AnnotationUtils;
2123
import org.springframework.jms.listener.MessageListenerContainer;
2224
import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
2325
import org.springframework.jms.support.converter.MessageConverter;
26+
import org.springframework.messaging.handler.annotation.SendTo;
2427
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
2528
import org.springframework.util.Assert;
29+
import org.springframework.util.StringUtils;
2630

2731
/**
2832
* A {@link JmsListenerEndpoint} providing the method to invoke to process
@@ -37,8 +41,6 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
3741

3842
private Method method;
3943

40-
private String responseDestination;
41-
4244
private JmsHandlerMethodFactory jmsHandlerMethodFactory;
4345

4446
/**
@@ -64,20 +66,6 @@ public Method getMethod() {
6466
return method;
6567
}
6668

67-
/**
68-
* Set the name of the default response destination to send response messages to.
69-
*/
70-
public void setResponseDestination(String responseDestination) {
71-
this.responseDestination = responseDestination;
72-
}
73-
74-
/**
75-
* Return the name of the default response destination to send response messages to.
76-
*/
77-
public String getResponseDestination() {
78-
return responseDestination;
79-
}
80-
8169
/**
8270
* Set the {@link DefaultJmsHandlerMethodFactory} to use to build the
8371
* {@link InvocableHandlerMethod} responsible to manage the invocation
@@ -91,12 +79,12 @@ public void setJmsHandlerMethodFactory(JmsHandlerMethodFactory jmsHandlerMethodF
9179
protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
9280
Assert.state(jmsHandlerMethodFactory != null,
9381
"Could not create message listener, message listener factory not set.");
94-
MessagingMessageListenerAdapter messageListener = new MessagingMessageListenerAdapter();
82+
MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
9583
InvocableHandlerMethod invocableHandlerMethod =
9684
jmsHandlerMethodFactory.createInvocableHandlerMethod(getBean(), getMethod());
9785
messageListener.setHandlerMethod(invocableHandlerMethod);
98-
String responseDestination = getResponseDestination();
99-
if (responseDestination != null) {
86+
String responseDestination = getDefaultResponseDestination();
87+
if (StringUtils.hasText(responseDestination)) {
10088
if (isQueue()) {
10189
messageListener.setDefaultResponseQueueName(responseDestination);
10290
}
@@ -111,6 +99,26 @@ protected MessagingMessageListenerAdapter createMessageListener(MessageListenerC
11199
return messageListener;
112100
}
113101

102+
/**
103+
* Create an empty {@link MessagingMessageListenerAdapter} instance.
104+
*/
105+
protected MessagingMessageListenerAdapter createMessageListenerInstance() {
106+
return new MessagingMessageListenerAdapter();
107+
}
108+
109+
private String getDefaultResponseDestination() {
110+
SendTo ann = AnnotationUtils.getAnnotation(getMethod(), SendTo.class);
111+
if (ann != null) {
112+
Object[] destinations = ann.value();
113+
if (destinations.length != 1) {
114+
throw new IllegalStateException("Invalid @" + SendTo.class.getSimpleName() + " annotation on '"
115+
+ getMethod() + "' one destination must be set (got " + Arrays.toString(destinations) + ")");
116+
}
117+
return (String) destinations[0];
118+
}
119+
return null;
120+
}
121+
114122
@Override
115123
protected StringBuilder getEndpointDescription() {
116124
return super.getEndpointDescription()

spring-jms/src/main/java/org/springframework/jms/support/JmsMessageHeaderAccessor.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,47 +51,82 @@ public static JmsMessageHeaderAccessor wrap(Message<?> message) {
5151
}
5252

5353

54-
@Override
55-
public Object getReplyChannel() {
56-
return getReplyTo();
57-
}
58-
54+
/**
55+
* Return the {@link JmsHeaders#CORRELATION_ID correlationId}.
56+
* @see JmsHeaders#CORRELATION_ID
57+
*/
5958
public String getCorrelationId() {
6059
return (String) getHeader(JmsHeaders.CORRELATION_ID);
6160
}
6261

62+
/**
63+
* Return the {@link JmsHeaders#DESTINATION destination}.
64+
* @see JmsHeaders#DESTINATION
65+
*/
6366
public Destination getDestination() {
6467
return (Destination) getHeader(JmsHeaders.DESTINATION);
6568
}
6669

70+
/**
71+
* Return the {@link JmsHeaders#DELIVERY_MODE delivery mode}.
72+
* @see JmsHeaders#DELIVERY_MODE
73+
*/
6774
public Integer getDeliveryMode() {
6875
return (Integer) getHeader(JmsHeaders.DELIVERY_MODE);
6976
}
7077

78+
/**
79+
* Return the message {@link JmsHeaders#EXPIRATION expiration}.
80+
* @see JmsHeaders#EXPIRATION
81+
*/
7182
public Long getExpiration() {
7283
return (Long) getHeader(JmsHeaders.EXPIRATION);
7384
}
7485

86+
/**
87+
* Return the {@link JmsHeaders#MESSAGE_ID message id}.
88+
* @see JmsHeaders#MESSAGE_ID
89+
*/
7590
public String getMessageId() {
7691
return (String) getHeader(JmsHeaders.MESSAGE_ID);
7792
}
7893

94+
/**
95+
* Return the {@link JmsHeaders#PRIORITY}.
96+
* @see JmsHeaders#PRIORITY
97+
*/
7998
public Integer getPriority() {
8099
return (Integer) getHeader(JmsHeaders.PRIORITY);
81100
}
82101

102+
/**
103+
* Return the {@link JmsHeaders#REPLY_TO reply to}.
104+
* @see JmsHeaders#REPLY_TO
105+
*/
83106
public Destination getReplyTo() {
84107
return (Destination) getHeader(JmsHeaders.REPLY_TO);
85108
}
86109

110+
/**
111+
* Return the {@link JmsHeaders#REDELIVERED redelivered} flag.
112+
* @see JmsHeaders#REDELIVERED
113+
*/
87114
public Boolean getRedelivered() {
88115
return (Boolean) getHeader(JmsHeaders.REDELIVERED);
89116
}
90117

118+
/**
119+
* Return the {@link JmsHeaders#TYPE type}.
120+
* @see JmsHeaders#TYPE
121+
*/
91122
public String getType() {
92123
return (String) getHeader(JmsHeaders.TYPE);
93124
}
94125

126+
/**
127+
* Return the {@link JmsHeaders#TIMESTAMP timestamp}.
128+
* @see JmsHeaders#TIMESTAMP
129+
*/
95130
public Long getTimestamp() {
96131
return (Long) getHeader(JmsHeaders.TIMESTAMP);
97132
}

spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public void testFullConfiguration(ApplicationContext context) {
114114
static class FullBean {
115115

116116
@JmsListener(id = "listener1", containerFactory = "simpleFactory", destination = "queueIn",
117-
responseDestination = "queueOut", selector = "mySelector", subscription = "mySubscription")
117+
selector = "mySelector", subscription = "mySubscription")
118118
public String fullHandle(String msg) {
119119
return "reply";
120120
}

spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public void simpleMessageListener() {
5858
MethodJmsListenerEndpoint methodEndpoint = (MethodJmsListenerEndpoint) endpoint;
5959
assertNotNull(methodEndpoint.getBean());
6060
assertNotNull(methodEndpoint.getMethod());
61-
assertNull(methodEndpoint.getResponseDestination());
6261
assertTrue(methodEndpoint.isQueue());
6362
assertTrue("Should have been started " + container, container.isStarted());
6463

spring-jms/src/test/java/org/springframework/jms/config/MethodJmsListenerEndpointTests.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828

2929
import javax.jms.Destination;
30+
import javax.jms.InvalidDestinationException;
3031
import javax.jms.JMSException;
3132
import javax.jms.ObjectMessage;
3233
import javax.jms.QueueSender;
@@ -48,12 +49,14 @@
4849
import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
4950
import org.springframework.jms.support.JmsMessageHeaderAccessor;
5051
import org.springframework.jms.support.converter.JmsHeaders;
52+
import org.springframework.jms.support.destination.DestinationResolver;
5153
import org.springframework.messaging.Message;
5254
import org.springframework.messaging.MessageHeaders;
5355
import org.springframework.messaging.converter.MessageConversionException;
5456
import org.springframework.messaging.handler.annotation.Header;
5557
import org.springframework.messaging.handler.annotation.Headers;
5658
import org.springframework.messaging.handler.annotation.Payload;
59+
import org.springframework.messaging.handler.annotation.SendTo;
5760
import org.springframework.messaging.handler.annotation.support.MethodArgumentTypeMismatchException;
5861
import org.springframework.util.ReflectionUtils;
5962
import org.springframework.validation.Errors;
@@ -99,7 +102,6 @@ public void createMessageListener() {
99102
endpoint.setBean(this);
100103
endpoint.setMethod(getTestMethod());
101104
endpoint.setJmsHandlerMethodFactory(factory);
102-
endpoint.setResponseDestination("myResponseQueue");
103105

104106
assertNotNull(endpoint.createMessageListener(container));
105107
}
@@ -217,6 +219,56 @@ public void processAndReply() throws JMSException {
217219
verify(queueSender).close();
218220
}
219221

222+
@Test
223+
public void processAndReplyWithSendTo() throws JMSException {
224+
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
225+
String body = "echo text";
226+
String correlationId = "link-1234";
227+
Destination replyDestination = new Destination() {};
228+
229+
DestinationResolver destinationResolver = mock(DestinationResolver.class);
230+
TextMessage reply = mock(TextMessage.class);
231+
QueueSender queueSender = mock(QueueSender.class);
232+
Session session = mock(Session.class);
233+
234+
given(destinationResolver.resolveDestinationName(session, "replyDestination", false))
235+
.willReturn(replyDestination);
236+
given(session.createTextMessage(body)).willReturn(reply);
237+
given(session.createProducer(replyDestination)).willReturn(queueSender);
238+
239+
listener.setDestinationResolver(destinationResolver);
240+
StubTextMessage inputMessage = createSimpleJmsTextMessage(body);
241+
inputMessage.setJMSCorrelationID(correlationId);
242+
listener.onMessage(inputMessage, session);
243+
assertDefaultListenerMethodInvocation();
244+
245+
verify(destinationResolver).resolveDestinationName(session, "replyDestination", false);
246+
verify(reply).setJMSCorrelationID(correlationId);
247+
verify(queueSender).send(reply);
248+
verify(queueSender).close();
249+
}
250+
251+
@Test
252+
public void emptySendTo() throws JMSException {
253+
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
254+
255+
TextMessage reply = mock(TextMessage.class);
256+
Session session = mock(Session.class);
257+
given(session.createTextMessage("content")).willReturn(reply);
258+
259+
thrown.expect(ListenerExecutionFailedException.class);
260+
thrown.expectCause(Matchers.isA(InvalidDestinationException.class));
261+
listener.onMessage(createSimpleJmsTextMessage("content"), session);
262+
}
263+
264+
@Test
265+
public void invalidSendTo() {
266+
thrown.expect(IllegalStateException.class);
267+
thrown.expectMessage("firstDestination");
268+
thrown.expectMessage("secondDestination");
269+
createDefaultInstance(String.class);
270+
}
271+
220272
@Test
221273
public void validatePayloadValid() throws JMSException {
222274
String methodName = "validatePayload";
@@ -394,6 +446,24 @@ public String processAndReply(@Payload String content) {
394446
return content;
395447
}
396448

449+
@SendTo("replyDestination")
450+
public String processAndReplyWithSendTo(String content) {
451+
invocations.put("processAndReplyWithSendTo", true);
452+
return content;
453+
}
454+
455+
@SendTo("")
456+
public String emptySendTo(String content) {
457+
invocations.put("emptySendTo", true);
458+
return content;
459+
}
460+
461+
@SendTo({"firstDestination", "secondDestination"})
462+
public String invalidSendTo(String content) {
463+
invocations.put("invalidSendTo", true);
464+
return content;
465+
}
466+
397467
public void validatePayload(@Validated String payload) {
398468
invocations.put("validatePayload", true);
399469
}

spring-jms/src/test/java/org/springframework/jms/support/JmsMessageHeaderAccessorTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,12 @@ public void validateJmsHeaders() throws JMSException {
6565
assertEquals("abcd-1234", headerAccessor.getMessageId());
6666
assertEquals(Integer.valueOf(9), headerAccessor.getPriority());
6767
assertEquals(replyTo, headerAccessor.getReplyTo());
68-
assertEquals(replyTo, headerAccessor.getReplyChannel());
6968
assertEquals(true, headerAccessor.getRedelivered());
7069
assertEquals("type", headerAccessor.getType());
7170
assertEquals(4567L, headerAccessor.getTimestamp(), 0.0);
71+
72+
// Making sure replyChannel is not mixed with replyTo
73+
assertNull(headerAccessor.getReplyChannel());
74+
7275
}
7376
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -26,7 +26,11 @@
2626

2727
/**
2828
* Annotation that indicates a method's return value should be converted to
29-
* a {@link Message} and sent to the specified destination.
29+
* a {@link Message} if necessary and sent to the specified destination.
30+
*
31+
* <p>In a typical request/reply scenario, the incoming {@link Message} may
32+
* convey the destination to use for the reply. In that case, that destination
33+
* should take precedence.
3034
*
3135
* @author Rossen Stoyanchev
3236
* @since 4.0

0 commit comments

Comments
 (0)