INT-3944: Async JMS Outbound Gateway
JIRA: https://jira.spring.io/browse/INT-3944 Polishing (PR comments) and Doc Polish Polishing - PR Comments Remove custom `async` boolean in favor of the now public setter. Add exception to `JmsException` hierarchy. INT-3944: Polishing * Rename `asyncReplySupported` just to `async`. As well as its getter and setter. Plus fix docs on the matter * Polishing for the `JmsOutboundGateway` according PR comments * Rework `JmsOutboundGateway` to return `AbstractIntegrationMessageBuilder` instead of `Message` since `AbstractMessageProducingHandler` rebuilds `Message` for a new one to copy headers from request * Add `getPayload()` and `getHeaders()` to the `AbstractIntegrationMessageBuilder` to make the `Routing Slip` users happy, when the `AbstractIntegrationMessageBuilder` is pushed to the `Routing Slip path` function * Fix race condition in the `ChatMessageListeningEndpointTests`: the `stanza` parsing is done in the different Thread. Doc Polishing (Routing Slip) Fix timing issue in the `LastModifiedFileListFilterTests`: `Thread.sleep()` not always reflects the reality. Switch to the "past simulation" via explicit `File.setLastModified()`
This commit is contained in:
committed by
Artem Bilan
parent
0fd72d2d18
commit
4bfcdb9dfa
@@ -51,6 +51,7 @@ import org.springframework.integration.MessageTimeoutException;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
import org.springframework.integration.jms.util.JmsAdapterUtils;
|
||||
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
|
||||
import org.springframework.jms.connection.ConnectionFactoryUtils;
|
||||
import org.springframework.jms.listener.DefaultMessageListenerContainer;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
@@ -66,6 +67,7 @@ import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
|
||||
/**
|
||||
* An outbound Messaging Gateway for request/reply JMS.
|
||||
@@ -79,6 +81,23 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler implements Lifecycle, MessageListener {
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
private final AtomicLong correlationId = new AtomicLong();
|
||||
|
||||
private final String gatewayCorrelation = UUID.randomUUID().toString();
|
||||
|
||||
private final Map<String, LinkedBlockingQueue<javax.jms.Message>> replies =
|
||||
new ConcurrentHashMap<String, LinkedBlockingQueue<javax.jms.Message>>();
|
||||
|
||||
private final ConcurrentHashMap<String, TimedReply> earlyOrLateReplies =
|
||||
new ConcurrentHashMap<String, JmsOutboundGateway.TimedReply>();
|
||||
|
||||
private final Map<String, SettableListenableFuture<AbstractIntegrationMessageBuilder<?>>> futures =
|
||||
new ConcurrentHashMap<String, SettableListenableFuture<AbstractIntegrationMessageBuilder<?>>>();
|
||||
|
||||
private final Object lifeCycleMonitor = new Object();
|
||||
|
||||
private volatile Destination requestDestination;
|
||||
|
||||
private volatile String requestDestinationName;
|
||||
@@ -127,24 +146,10 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
|
||||
private volatile boolean useReplyContainer;
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
private volatile boolean active;
|
||||
|
||||
private final AtomicLong correlationId = new AtomicLong();
|
||||
|
||||
private final String gatewayCorrelation = UUID.randomUUID().toString();
|
||||
|
||||
private final Map<String, LinkedBlockingQueue<javax.jms.Message>> replies =
|
||||
new ConcurrentHashMap<String, LinkedBlockingQueue<javax.jms.Message>>();
|
||||
|
||||
private final ConcurrentHashMap<String, TimedReply> earlyOrLateReplies =
|
||||
new ConcurrentHashMap<String, JmsOutboundGateway.TimedReply>();
|
||||
|
||||
private volatile ScheduledFuture<?> reaper;
|
||||
|
||||
private final Object lifeCycleMonitor = new Object();
|
||||
|
||||
private volatile boolean requiresReply;
|
||||
|
||||
private long lastSend;
|
||||
@@ -210,7 +215,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
*/
|
||||
public void setRequestDestinationExpression(Expression requestDestinationExpression) {
|
||||
Assert.notNull(requestDestinationExpression, "'requestDestinationExpression' must not be null");
|
||||
this.requestDestinationExpressionProcessor = new ExpressionEvaluatingMessageProcessor<Object>(requestDestinationExpression);
|
||||
this.requestDestinationExpressionProcessor =
|
||||
new ExpressionEvaluatingMessageProcessor<Object>(requestDestinationExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,7 +415,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
|
||||
/**
|
||||
* @param replyContainerProperties the replyContainerproperties to set
|
||||
* @param replyContainerProperties the replyContainerProperties to set
|
||||
*/
|
||||
public void setReplyContainerProperties(ReplyContainerProperties replyContainerProperties) {
|
||||
this.replyContainerProperties = replyContainerProperties;
|
||||
@@ -473,7 +479,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
return this.resolveRequestDestination((String) result, session);
|
||||
}
|
||||
throw new MessageDeliveryException(message,
|
||||
"Evaluation of requestDestinationExpression failed to produce a Destination or destination name. Result was: " + result);
|
||||
"Evaluation of requestDestinationExpression failed " +
|
||||
"to produce a Destination or destination name. Result was: " + result);
|
||||
}
|
||||
throw new MessageDeliveryException(message,
|
||||
"No requestDestination, requestDestinationName, or requestDestinationExpression has been configured.");
|
||||
@@ -502,7 +509,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
return this.resolveReplyDestination((String) result, session);
|
||||
}
|
||||
throw new MessageDeliveryException(message,
|
||||
"Evaluation of replyDestinationExpression failed to produce a Destination or destination name. Result was: " + result);
|
||||
"Evaluation of replyDestinationExpression failed to produce a Destination or destination name. " +
|
||||
"Result was: " + result);
|
||||
}
|
||||
return session.createTemporaryQueue();
|
||||
}
|
||||
@@ -524,7 +532,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
Assert.isTrue(this.requestDestination != null
|
||||
^ this.requestDestinationName != null
|
||||
^ this.requestDestinationExpressionProcessor != null,
|
||||
"Exactly one of 'requestDestination', 'requestDestinationName', or 'requestDestinationExpression' is required.");
|
||||
"Exactly one of 'requestDestination', 'requestDestinationName', " +
|
||||
"or 'requestDestinationExpression' is required.");
|
||||
if (this.requestDestinationExpressionProcessor != null) {
|
||||
this.requestDestinationExpressionProcessor.setBeanFactory(getBeanFactory());
|
||||
this.requestDestinationExpressionProcessor.setConversionService(getConversionService());
|
||||
@@ -541,7 +550,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
(this.replyDestination != null || this.replyDestinationName != null) ||
|
||||
this.replyDestinationExpressionProcessor != null)) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("The gateway cannot use a reply listener container with a specified destination(Name/Expression) " +
|
||||
logger.warn("The gateway cannot use a reply listener container with a specified " +
|
||||
"destination(Name/Expression) " +
|
||||
"without a 'correlation-key'; " +
|
||||
"a container will NOT be used; " +
|
||||
"to avoid this problem, set the 'correlation-key' attribute; " +
|
||||
@@ -560,6 +570,16 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
setContainerProperties(container);
|
||||
container.afterPropertiesSet();
|
||||
this.replyContainer = container;
|
||||
if (isAsync() && this.correlationKey == null) {
|
||||
logger.warn("'async=true' requires a correlationKey; ignored");
|
||||
setAsync(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isAsync()) {
|
||||
logger.warn("'async=true' is ignored when a reply container is not being used");
|
||||
setAsync(false);
|
||||
}
|
||||
}
|
||||
this.initialized = true;
|
||||
}
|
||||
@@ -611,7 +631,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
container.setRecoveryInterval(this.replyContainerProperties.getRecoveryInterval());
|
||||
}
|
||||
if (StringUtils.hasText(this.replyContainerProperties.getSessionAcknowledgeModeName())) {
|
||||
Integer acknowledgeMode = JmsAdapterUtils.parseAcknowledgeMode(this.replyContainerProperties.getSessionAcknowledgeModeName());
|
||||
Integer acknowledgeMode = JmsAdapterUtils.parseAcknowledgeMode(
|
||||
this.replyContainerProperties.getSessionAcknowledgeModeName());
|
||||
if (acknowledgeMode != null) {
|
||||
if (JmsAdapterUtils.SESSION_TRANSACTED == acknowledgeMode) {
|
||||
container.setSessionTransacted(true);
|
||||
@@ -659,7 +680,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
else {
|
||||
Assert.state(taskScheduler != null, "'taskScheduler' is required.");
|
||||
}
|
||||
if (this.receiveTimeout >= 0) {
|
||||
if (!isAsync() && this.receiveTimeout >= 0) {
|
||||
Assert.state(taskScheduler != null, "'taskScheduler' is required.");
|
||||
this.reaper = taskScheduler.schedule(new LateReplyReaper(), new Date());
|
||||
}
|
||||
@@ -675,7 +696,9 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
if (this.replyContainer != null) {
|
||||
this.replyContainer.stop();
|
||||
this.deleteDestinationIfTemporary(this.replyContainer.getDestination());
|
||||
this.reaper.cancel(false);
|
||||
if (this.reaper != null) {
|
||||
this.reaper.cancel(false);
|
||||
}
|
||||
}
|
||||
if (this.idleTask != null) {
|
||||
this.idleTask.cancel(true);
|
||||
@@ -691,15 +714,14 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(final Message<?> message) {
|
||||
protected Object handleRequestMessage(final Message<?> requestMessage) {
|
||||
if (!this.initialized) {
|
||||
this.afterPropertiesSet();
|
||||
afterPropertiesSet();
|
||||
}
|
||||
final Message<?> requestMessage = this.getMessageBuilderFactory().fromMessage(message).build();
|
||||
try {
|
||||
javax.jms.Message jmsReply;
|
||||
Object reply;
|
||||
if (this.replyContainer == null) {
|
||||
jmsReply = this.sendAndReceiveWithoutContainer(requestMessage);
|
||||
reply = sendAndReceiveWithoutContainer(requestMessage);
|
||||
}
|
||||
else {
|
||||
if (this.idleReplyContainerTimeout > 0) {
|
||||
@@ -715,45 +737,53 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
}
|
||||
}
|
||||
jmsReply = this.sendAndReceiveWithContainer(requestMessage);
|
||||
reply = this.sendAndReceiveWithContainer(requestMessage);
|
||||
}
|
||||
if (jmsReply == null) {
|
||||
if (reply == null) {
|
||||
if (this.requiresReply) {
|
||||
throw new MessageTimeoutException(message,
|
||||
throw new MessageTimeoutException(requestMessage,
|
||||
"failed to receive JMS response within timeout of: " + this.receiveTimeout + "ms");
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Object result = jmsReply;
|
||||
if (this.extractReplyPayload) {
|
||||
result = this.messageConverter.fromMessage(jmsReply);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("converted JMS Message [" + jmsReply + "] to integration Message payload [" + result + "]");
|
||||
}
|
||||
}
|
||||
Map<String, Object> jmsReplyHeaders = this.headerMapper.toHeaders(jmsReply);
|
||||
|
||||
if (this.replyContainer != null && this.correlationKey != null) {
|
||||
// do not propagate back the gateway's internal correlation id
|
||||
jmsReplyHeaders.remove(this.correlationKey);
|
||||
}
|
||||
Message<?> replyMessage = null;
|
||||
if (result instanceof Message){
|
||||
replyMessage = this.getMessageBuilderFactory().fromMessage((Message<?>) result).copyHeaders(jmsReplyHeaders).build();
|
||||
if (reply instanceof javax.jms.Message) {
|
||||
return buildReply((javax.jms.Message) reply);
|
||||
}
|
||||
else {
|
||||
replyMessage = this.getMessageBuilderFactory().withPayload(result).copyHeaders(jmsReplyHeaders).build();
|
||||
return reply;
|
||||
}
|
||||
return replyMessage;
|
||||
}
|
||||
catch (JMSException e) {
|
||||
throw new MessageHandlingException(requestMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
private javax.jms.Message sendAndReceiveWithContainer(Message<?> requestMessage) throws JMSException {
|
||||
private AbstractIntegrationMessageBuilder<?> buildReply(javax.jms.Message jmsReply) throws JMSException {
|
||||
Object result = jmsReply;
|
||||
if (this.extractReplyPayload) {
|
||||
result = this.messageConverter.fromMessage(jmsReply);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("converted JMS Message [" + jmsReply + "] to integration Message payload [" + result + "]");
|
||||
}
|
||||
}
|
||||
Map<String, Object> jmsReplyHeaders = this.headerMapper.toHeaders(jmsReply);
|
||||
|
||||
if (this.replyContainer != null && this.correlationKey != null) {
|
||||
// do not propagate back the gateway's internal correlation id
|
||||
jmsReplyHeaders.remove(this.correlationKey);
|
||||
}
|
||||
if (result instanceof Message){
|
||||
return getMessageBuilderFactory().fromMessage((Message<?>) result).copyHeaders(jmsReplyHeaders);
|
||||
}
|
||||
else {
|
||||
return getMessageBuilderFactory().withPayload(result).copyHeaders(jmsReplyHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
private Object sendAndReceiveWithContainer(Message<?> requestMessage) throws JMSException {
|
||||
Connection connection = this.createConnection();
|
||||
Session session = null;
|
||||
Destination replyTo = this.replyContainer.getReplyDestination();
|
||||
@@ -782,7 +812,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
Destination requestDestination = this.determineRequestDestination(requestMessage, session);
|
||||
|
||||
javax.jms.Message reply = null;
|
||||
Object reply = null;
|
||||
if (this.correlationKey == null) {
|
||||
/*
|
||||
* Remove any existing correlation id that was mapped from the inbound message
|
||||
@@ -798,8 +828,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
* Remove the gateway's internal correlation Id to avoid conflicts with an upstream
|
||||
* gateway.
|
||||
*/
|
||||
if (reply != null) {
|
||||
reply.setJMSCorrelationID(null);
|
||||
if (reply instanceof javax.jms.Message) {
|
||||
((javax.jms.Message) reply).setJMSCorrelationID(null);
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
@@ -840,13 +870,16 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
javax.jms.Message replyMessage = null;
|
||||
Destination requestDestination = this.determineRequestDestination(requestMessage, session);
|
||||
if (this.correlationKey != null) {
|
||||
replyMessage = this.doSendAndReceiveWithGeneratedCorrelationId(requestDestination, jmsRequest, replyTo, session, priority);
|
||||
replyMessage = doSendAndReceiveWithGeneratedCorrelationId(requestDestination, jmsRequest, replyTo,
|
||||
session, priority);
|
||||
}
|
||||
else if (replyTo instanceof TemporaryQueue || replyTo instanceof TemporaryTopic) {
|
||||
replyMessage = this.doSendAndReceiveWithTemporaryReplyToDestination(requestDestination, jmsRequest, replyTo, session, priority);
|
||||
replyMessage = doSendAndReceiveWithTemporaryReplyToDestination(requestDestination, jmsRequest, replyTo,
|
||||
session, priority);
|
||||
}
|
||||
else {
|
||||
replyMessage = this.doSendAndReceiveWithMessageIdCorrelation(requestDestination, jmsRequest, replyTo, session, priority);
|
||||
replyMessage = doSendAndReceiveWithMessageIdCorrelation(requestDestination, jmsRequest, replyTo,
|
||||
session, priority);
|
||||
}
|
||||
return replyMessage;
|
||||
}
|
||||
@@ -858,7 +891,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the MessageConsumer before sending the request Message since we are generating our own correlationId value for the MessageSelector.
|
||||
* Creates the MessageConsumer before sending the request Message since we are generating
|
||||
* our own correlationId value for the MessageSelector.
|
||||
*/
|
||||
private javax.jms.Message doSendAndReceiveWithGeneratedCorrelationId(Destination requestDestination,
|
||||
javax.jms.Message jmsRequest, Destination replyTo, Session session, int priority) throws JMSException {
|
||||
@@ -911,7 +945,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the MessageConsumer after sending the request Message since we need the MessageID for correlation with a MessageSelector.
|
||||
* Creates the MessageConsumer after sending the request Message since we need
|
||||
* the MessageID for correlation with a MessageSelector.
|
||||
*/
|
||||
private javax.jms.Message doSendAndReceiveWithMessageIdCorrelation(Destination requestDestination,
|
||||
javax.jms.Message jmsRequest, Destination replyTo, Session session, int priority) throws JMSException {
|
||||
@@ -1009,7 +1044,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
}
|
||||
|
||||
private javax.jms.Message doSendAndReceiveAsync(Destination requestDestination, javax.jms.Message jmsRequest, Session session, int priority) throws JMSException {
|
||||
private Object doSendAndReceiveAsync(Destination requestDestination, javax.jms.Message jmsRequest, Session session,
|
||||
int priority) throws JMSException {
|
||||
String correlationId = null;
|
||||
MessageProducer messageProducer = null;
|
||||
try {
|
||||
@@ -1026,19 +1062,32 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
*/
|
||||
jmsRequest.setJMSCorrelationID(null);
|
||||
}
|
||||
LinkedBlockingQueue<javax.jms.Message> replyQueue = new LinkedBlockingQueue<javax.jms.Message>(1);
|
||||
LinkedBlockingQueue<javax.jms.Message> replyQueue = null;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(this.getComponentName() + " Sending message with correlationId " + correlationId);
|
||||
}
|
||||
this.replies.put(correlationId, replyQueue);
|
||||
SettableListenableFuture<AbstractIntegrationMessageBuilder<?>> future = null;
|
||||
boolean async = isAsync();
|
||||
if (!async) {
|
||||
replyQueue = new LinkedBlockingQueue<javax.jms.Message>(1);
|
||||
this.replies.put(correlationId, replyQueue);
|
||||
}
|
||||
else {
|
||||
future = createFuture(correlationId);
|
||||
}
|
||||
|
||||
this.sendRequestMessage(jmsRequest, messageProducer, priority);
|
||||
|
||||
return obtainReplyFromContainer(correlationId, replyQueue);
|
||||
if (async) {
|
||||
return future;
|
||||
}
|
||||
else {
|
||||
return obtainReplyFromContainer(correlationId, replyQueue);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageProducer(messageProducer);
|
||||
if (correlationId != null) {
|
||||
if (correlationId != null && !isAsync()) {
|
||||
this.replies.remove(correlationId);
|
||||
}
|
||||
}
|
||||
@@ -1103,16 +1152,58 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (reply == null) {
|
||||
logger.debug(this.getComponentName() + " Timed out waiting for reply with CorrelationId " + correlationId);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(this.getComponentName() + " Timed out waiting for reply with CorrelationId "
|
||||
+ correlationId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug(this.getComponentName() + " Obtained reply with CorrelationId " + correlationId);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(this.getComponentName() + " Obtained reply with CorrelationId " + correlationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
private void sendRequestMessage(javax.jms.Message jmsRequest, MessageProducer messageProducer, int priority) throws JMSException {
|
||||
private SettableListenableFuture<AbstractIntegrationMessageBuilder<?>> createFuture(final String correlationId) {
|
||||
SettableListenableFuture<AbstractIntegrationMessageBuilder<?>> future =
|
||||
new SettableListenableFuture<AbstractIntegrationMessageBuilder<?>>();
|
||||
this.futures.put(correlationId, future);
|
||||
if (this.receiveTimeout > 0) {
|
||||
getTaskScheduler().schedule(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
expire(correlationId);
|
||||
}
|
||||
|
||||
}, new Date(System.currentTimeMillis() + this.receiveTimeout));
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
private void expire(String correlationId) {
|
||||
final SettableListenableFuture<AbstractIntegrationMessageBuilder<?>> future = this.futures.remove(correlationId);
|
||||
if (future != null) {
|
||||
try {
|
||||
if (getRequiresReply()) {
|
||||
future.setException(new JmsTimeoutException("No reply in " + this.receiveTimeout + " ms"));
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reply expired and reply not required for " + correlationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Exception while expiring future");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendRequestMessage(javax.jms.Message jmsRequest, MessageProducer messageProducer, int priority)
|
||||
throws JMSException {
|
||||
if (this.explicitQosEnabled) {
|
||||
messageProducer.send(jmsRequest, this.deliveryMode, priority, this.timeToLive);
|
||||
}
|
||||
@@ -1181,6 +1272,33 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
correlationId = message.getStringProperty(this.correlationKey);
|
||||
}
|
||||
Assert.state(correlationId != null, "Message with no correlationId received");
|
||||
if (isAsync()) {
|
||||
onMessageAsync(message, correlationId);
|
||||
}
|
||||
else {
|
||||
onMessageSync(message, correlationId);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to consume reply with correlationId " + correlationId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageAsync(javax.jms.Message message, String correlationId) throws Exception {
|
||||
SettableListenableFuture<AbstractIntegrationMessageBuilder<?>> future = this.futures.remove(correlationId);
|
||||
if (future != null) {
|
||||
message.setJMSCorrelationID(null);
|
||||
future.set(buildReply(message));
|
||||
}
|
||||
else {
|
||||
logger.warn("Late reply for " + correlationId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageSync(javax.jms.Message message, String correlationId) {
|
||||
try {
|
||||
LinkedBlockingQueue<javax.jms.Message> queue = this.replies.get(correlationId);
|
||||
if (queue == null) {
|
||||
if (this.correlationKey != null) {
|
||||
@@ -1330,7 +1448,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Running late reply reaper");
|
||||
}
|
||||
Iterator<Entry<String, TimedReply>> lateReplyIterator = JmsOutboundGateway.this.earlyOrLateReplies.entrySet().iterator();
|
||||
Iterator<Entry<String, TimedReply>> lateReplyIterator =
|
||||
JmsOutboundGateway.this.earlyOrLateReplies.entrySet().iterator();
|
||||
long now = System.currentTimeMillis();
|
||||
long expired = now - (JmsOutboundGateway.this.receiveTimeout * 2);
|
||||
while (lateReplyIterator.hasNext()) {
|
||||
@@ -1355,7 +1474,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized(JmsOutboundGateway.this.lifeCycleMonitor) {
|
||||
if (System.currentTimeMillis() - JmsOutboundGateway.this.lastSend > JmsOutboundGateway.this.idleReplyContainerTimeout
|
||||
if (System.currentTimeMillis() - JmsOutboundGateway.this.lastSend >
|
||||
JmsOutboundGateway.this.idleReplyContainerTimeout
|
||||
&& JmsOutboundGateway.this.replies.size() == 0) {
|
||||
if (JmsOutboundGateway.this.replyContainer.isRunning()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 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.integration.jms;
|
||||
|
||||
import org.springframework.jms.JmsException;
|
||||
|
||||
/**
|
||||
* A timeout occurred within an async gateway.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class JmsTimeoutException extends JmsException {
|
||||
|
||||
public JmsTimeoutException(String description) {
|
||||
super(description);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,6 +69,7 @@ public class JmsOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "idle-reply-listener-timeout",
|
||||
"idleReplyContainerTimeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "async");
|
||||
|
||||
String deliveryPersistent = element.getAttribute("delivery-persistent");
|
||||
if (StringUtils.hasText(deliveryPersistent)) {
|
||||
|
||||
@@ -1128,6 +1128,16 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="async" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
When false (default), the requesting thread is suspended until a reply is received
|
||||
or a timeout occurs; when true, the requesting thread is released and the reply
|
||||
is returned on the listener container thread. Requires a reply-listener child
|
||||
element and a 'correlation-key'; otherwise this property is ignored.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
Reference in New Issue
Block a user