Merge pull request #718 from garyrussell-INT-2682
* garyrussell-INT-2682-2: INT-2682 JMS GW - Support Expression for replyDest
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -82,6 +82,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
|
||||
private volatile String replyDestinationName;
|
||||
|
||||
private volatile ExpressionEvaluatingMessageProcessor<?> replyDestinationExpressionProcessor;
|
||||
|
||||
private volatile DestinationResolver destinationResolver = new DynamicDestinationResolver();
|
||||
|
||||
private volatile boolean requestPubSubDomain;
|
||||
@@ -184,6 +186,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
* 'requestDestinationName' is required.
|
||||
*/
|
||||
public void setRequestDestinationExpression(Expression requestDestinationExpression) {
|
||||
Assert.notNull(requestDestinationExpression, "'requestDestinationExpression' must not be null");
|
||||
this.requestDestinationExpressionProcessor = new ExpressionEvaluatingMessageProcessor<Object>(requestDestinationExpression);
|
||||
}
|
||||
|
||||
@@ -206,6 +209,16 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
this.replyDestinationName = replyDestinationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SpEL Expression to be used for determining the reply Destination instance
|
||||
* or reply destination name. Either this or one of 'replyDestination' or
|
||||
* 'replyDestinationName' is required.
|
||||
*/
|
||||
public void setReplyDestinationExpression(Expression replyDestinationExpression) {
|
||||
Assert.notNull(replyDestinationExpression, "'replyDestinationExpression' must not be null");
|
||||
this.replyDestinationExpressionProcessor = new ExpressionEvaluatingMessageProcessor<Object>(replyDestinationExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the {@link DestinationResolver} to use when resolving either a
|
||||
* 'requestDestinationName' or 'replyDestinationName' value. The default
|
||||
@@ -369,7 +382,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
this.useReplyContainer = useReplyContainer;
|
||||
}
|
||||
|
||||
private Destination getRequestDestination(Message<?> message, Session session) throws JMSException {
|
||||
private Destination determineRequestDestination(Message<?> message, Session session) throws JMSException {
|
||||
if (this.requestDestination != null) {
|
||||
return this.requestDestination;
|
||||
}
|
||||
@@ -398,19 +411,34 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
session, requestDestinationName, this.requestPubSubDomain);
|
||||
}
|
||||
|
||||
private Destination determineReplyDestination(Session session) throws JMSException {
|
||||
private Destination determineReplyDestination(Message<?> message, Session session) throws JMSException {
|
||||
if (this.replyDestination != null) {
|
||||
return this.replyDestination;
|
||||
}
|
||||
if (this.replyDestinationName != null) {
|
||||
Assert.notNull(this.destinationResolver,
|
||||
"DestinationResolver is required when relying upon the 'replyDestinationName' property.");
|
||||
return this.destinationResolver.resolveDestinationName(
|
||||
session, this.replyDestinationName, this.replyPubSubDomain);
|
||||
return this.resolveReplyDestination(this.replyDestinationName, session);
|
||||
}
|
||||
if (this.replyDestinationExpressionProcessor != null) {
|
||||
Object result = this.replyDestinationExpressionProcessor.processMessage(message);
|
||||
if (result instanceof Destination) {
|
||||
return (Destination) result;
|
||||
}
|
||||
if (result instanceof String) {
|
||||
return this.resolveReplyDestination((String) result, session);
|
||||
}
|
||||
throw new MessageDeliveryException(message,
|
||||
"Evaluation of replyDestinationExpression failed to produce a Destination or destination name. Result was: " + result);
|
||||
}
|
||||
return session.createTemporaryQueue();
|
||||
}
|
||||
|
||||
private Destination resolveReplyDestination(String replyDestinationName, Session session) throws JMSException {
|
||||
Assert.notNull(this.destinationResolver,
|
||||
"DestinationResolver is required when relying upon the 'replyDestinationName' property.");
|
||||
return this.destinationResolver.resolveDestinationName(
|
||||
session, replyDestinationName, this.replyPubSubDomain);
|
||||
}
|
||||
|
||||
public int getPhase() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -439,14 +467,19 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
this.requestDestinationExpressionProcessor.setBeanFactory(getBeanFactory());
|
||||
this.requestDestinationExpressionProcessor.setConversionService(getConversionService());
|
||||
}
|
||||
if (this.replyDestinationExpressionProcessor != null) {
|
||||
this.replyDestinationExpressionProcessor.setBeanFactory(getBeanFactory());
|
||||
this.replyDestinationExpressionProcessor.setConversionService(getConversionService());
|
||||
}
|
||||
/*
|
||||
* This is needed because there is no way to detect 2 or more gateways using the same reply queue
|
||||
* with no correlation key.
|
||||
*/
|
||||
if (this.useReplyContainer && (this.correlationKey == null &&
|
||||
(this.replyDestination != null || this.replyDestinationName != null))) {
|
||||
(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) " +
|
||||
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; " +
|
||||
@@ -621,7 +654,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
if (priority == null) {
|
||||
priority = this.priority;
|
||||
}
|
||||
Destination requestDestination = this.getRequestDestination(requestMessage, session);
|
||||
Destination requestDestination = this.determineRequestDestination(requestMessage, session);
|
||||
|
||||
/*
|
||||
* Remove any existing correlation id that was mapped from the inbound message
|
||||
@@ -667,8 +700,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
// map headers
|
||||
headerMapper.fromHeaders(requestMessage.getHeaders(), jmsRequest);
|
||||
|
||||
// TODO: support a JmsReplyTo header in the SI Message?
|
||||
replyTo = this.determineReplyDestination(session);
|
||||
replyTo = this.determineReplyDestination(requestMessage, session);
|
||||
jmsRequest.setJMSReplyTo(replyTo);
|
||||
connection.start();
|
||||
|
||||
@@ -677,7 +709,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
|
||||
priority = this.priority;
|
||||
}
|
||||
javax.jms.Message replyMessage = null;
|
||||
Destination requestDestination = this.getRequestDestination(requestMessage, session);
|
||||
Destination requestDestination = this.determineRequestDestination(requestMessage, session);
|
||||
if (this.correlationKey != null) {
|
||||
replyMessage = this.doSendAndReceiveWithGeneratedCorrelationId(requestDestination, jmsRequest, replyTo, session, priority);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -44,29 +44,12 @@ public class JmsOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JmsOutboundGateway.class);
|
||||
builder.addPropertyReference("connectionFactory", element.getAttribute("connection-factory"));
|
||||
String requestDestination = element.getAttribute("request-destination");
|
||||
String requestDestinationName = element.getAttribute("request-destination-name");
|
||||
String requestDestinationExpression = element.getAttribute("request-destination-expression");
|
||||
boolean hasRequestDestination = StringUtils.hasText(requestDestination);
|
||||
boolean hasRequestDestinationName = StringUtils.hasText(requestDestinationName);
|
||||
boolean hasRequestDestinationExpression = StringUtils.hasText(requestDestinationExpression);
|
||||
if (!(hasRequestDestination ^ hasRequestDestinationName ^ hasRequestDestinationExpression)) {
|
||||
parserContext.getReaderContext().error("Exactly one of the 'request-destination', " +
|
||||
"'request-destination-name', or 'request-destination-expression' attributes is required.", element);
|
||||
}
|
||||
if (hasRequestDestination) {
|
||||
builder.addPropertyReference("requestDestination", requestDestination);
|
||||
}
|
||||
else if (hasRequestDestinationName) {
|
||||
builder.addPropertyValue("requestDestinationName", requestDestinationName);
|
||||
}
|
||||
else if (hasRequestDestinationExpression) {
|
||||
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionBuilder.addConstructorArgValue(requestDestinationExpression);
|
||||
builder.addPropertyValue("requestDestinationExpression", expressionBuilder.getBeanDefinition());
|
||||
}
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-destination");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-destination-name");
|
||||
parseDestination(element, parserContext, builder, "request-destination", "request-destination-name",
|
||||
"request-destination-expression", "requestDestination", "requestDestinationName",
|
||||
"requestDestinationExpression", true);
|
||||
parseDestination(element, parserContext, builder, "reply-destination", "reply-destination-name",
|
||||
"reply-destination-expression", "replyDestination", "replyDestinationName",
|
||||
"replyDestinationExpression", false);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "correlation-key");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
|
||||
@@ -104,6 +87,44 @@ public class JmsOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void parseDestination(Element element, ParserContext parserContext, BeanDefinitionBuilder builder,
|
||||
String destinationAttributeName, String destinationNameAttributeName, String destinationExpressionAttributeName,
|
||||
String destinationProperty, String destinationNameProperty, String destinationExpressionProperty,
|
||||
boolean oneRequired) {
|
||||
String destinationAttribute = element.getAttribute(destinationAttributeName);
|
||||
String destinationNameAttribute = element.getAttribute(destinationNameAttributeName);
|
||||
String destinationExpressionAttribute = element.getAttribute(destinationExpressionAttributeName);
|
||||
boolean hasDestination = StringUtils.hasText(destinationAttribute);
|
||||
boolean hasDestinationName = StringUtils.hasText(destinationNameAttribute);
|
||||
boolean hasDestinationExpression = StringUtils.hasText(destinationExpressionAttribute);
|
||||
int destCount = (hasDestination ? 1 : 0) +
|
||||
(hasDestinationName ? 1 : 0) +
|
||||
(hasDestinationExpression ? 1 : 0);
|
||||
if (oneRequired) {
|
||||
if (destCount != 1) {
|
||||
parserContext.getReaderContext().error("Exactly one of the '" + destinationAttribute + "', " +
|
||||
"'" + destinationNameAttributeName + "', or '" + destinationExpressionAttributeName + "' attributes is required.", element);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (destCount > 1) {
|
||||
parserContext.getReaderContext().error("Only one of the '" + destinationAttribute + "', " +
|
||||
"'" + destinationNameAttributeName + "', or '" + destinationExpressionAttributeName + "' attributes is allowed.", element);
|
||||
}
|
||||
}
|
||||
if (hasDestination) {
|
||||
builder.addPropertyReference(destinationProperty, destinationAttribute);
|
||||
}
|
||||
else if (hasDestinationName) {
|
||||
builder.addPropertyValue(destinationNameProperty, destinationNameAttribute);
|
||||
}
|
||||
else if (hasDestinationExpression) {
|
||||
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionBuilder.addConstructorArgValue(destinationExpressionAttribute);
|
||||
builder.addPropertyValue(destinationExpressionProperty, expressionBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseReplyContainer(BeanDefinitionBuilder gatewayBuilder, ParserContext parserContext, Element element) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JmsOutboundGateway.ReplyContainerProperties.class);
|
||||
Integer acknowledgeMode = JmsAdapterParserUtils.parseAcknowledgeMode(element, parserContext);
|
||||
|
||||
@@ -850,7 +850,8 @@
|
||||
<xsd:attribute name="request-pub-sub-domain" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
When resolving a request destination name (rather than having a 'request-destination' reference),
|
||||
When resolving a request destination name (rather than a 'request-destination' reference or
|
||||
a 'request-destination-expression' that evaluates to a destination),
|
||||
a true value here specifies that the DestinationResolver should resolve Topics rather than Queues.
|
||||
Default is false.
|
||||
</xsd:documentation>
|
||||
@@ -858,11 +859,13 @@
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-destination" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<xsd:documentation>
|
||||
A reference to a javax.jms.Destination by bean name. As an alternative to a bean
|
||||
reference, use 'reply-destination-name' and 'reply-pub-sub-domain' which will rely
|
||||
upon the DestinationResolver strategy (DynamicDestinationResolver by default).
|
||||
</xsd:documentation>
|
||||
This attribute is mutually exclusive with
|
||||
'reply-destination-name' and 'reply-destination-expression'.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="javax.jms.Destination"/>
|
||||
@@ -870,8 +873,36 @@
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-destination-name" type="xsd:string"/>
|
||||
<xsd:attribute name="reply-pub-sub-domain" type="xsd:string"/>
|
||||
<xsd:attribute name="reply-destination-name" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Name of a destination which will be used for the replyTo header. This name will be handled
|
||||
by this gateway's DestinationResolver. This attribute is mutually exclusive with
|
||||
'reply-destination' and 'reply-destination-expression'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-destination-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A SpEL expression to be evaluated at runtime against each Spring Integration request Message as
|
||||
the root object. The result should be either a Destination instance or a String representing
|
||||
the destination name. In the latter case, it will be passed to this adapter's DestinationResolver
|
||||
together with the reply-pub-sub-domain attribute.
|
||||
This attribute is mutually exclusive with 'reply-destination' and 'reply-destination-name'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-pub-sub-domain" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
When resolving a reply destination name (rather than a 'reply-destination' reference or
|
||||
a 'reply-destination-expression' that evaluates to a destination),
|
||||
a true value here specifies that the DestinationResolver should resolve Topics rather than Queues.
|
||||
Default is false.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="correlation-key" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
@@ -884,6 +915,10 @@
|
||||
of the request. If you want to store the outbound correlation UUID value in the
|
||||
actual "JMSCorrelationID" property, then set this value to "JMSCorrelationID".
|
||||
However, any other value will be treated as a JMS String Property.
|
||||
Note: when using a <reply-container/> the correlation data includes a UUID
|
||||
representing the gateway as well as a message identifier. For this reason, the use of a
|
||||
<reply-container/> requires the specification of a 'correlation-key' if an
|
||||
explicit reply-destination is provided.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -19,19 +19,29 @@ package org.springframework.integration.jms.config;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.jms.DeliveryMode;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessagingException;
|
||||
@@ -40,11 +50,13 @@ import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.core.SubscribableChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.endpoint.PollingConsumer;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.jms.JmsOutboundGateway;
|
||||
import org.springframework.integration.jms.StubMessageConverter;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.jms.listener.DefaultMessageListenerContainer;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
@@ -116,6 +128,81 @@ public class JmsOutboundGatewayParserTests {
|
||||
assertEquals(99, order);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayWithDest() {
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"jmsOutboundGatewayReplyDestOptions.xml", this.getClass());
|
||||
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("jmsGatewayDest");
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(endpoint);
|
||||
JmsOutboundGateway gateway = (JmsOutboundGateway) accessor.getPropertyValue("handler");
|
||||
accessor = new DirectFieldAccessor(gateway);
|
||||
assertSame(context.getBean("replyQueue"), accessor.getPropertyValue("replyDestination"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayWithDestName() {
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"jmsOutboundGatewayReplyDestOptions.xml", this.getClass());
|
||||
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("jmsGatewayDestName");
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(endpoint);
|
||||
JmsOutboundGateway gateway = (JmsOutboundGateway) accessor.getPropertyValue("handler");
|
||||
accessor = new DirectFieldAccessor(gateway);
|
||||
assertEquals("replyQueueName", accessor.getPropertyValue("replyDestinationName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayWithDestExpression() throws Exception {
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"jmsOutboundGatewayReplyDestOptions.xml", this.getClass());
|
||||
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("jmsGatewayDestExpression");
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(endpoint);
|
||||
JmsOutboundGateway gateway = (JmsOutboundGateway) accessor.getPropertyValue("handler");
|
||||
ExpressionEvaluatingMessageProcessor<?> processor = TestUtils.getPropertyValue(gateway, "replyDestinationExpressionProcessor",
|
||||
ExpressionEvaluatingMessageProcessor.class);
|
||||
Expression expression = TestUtils.getPropertyValue(gateway, "replyDestinationExpressionProcessor.expression",
|
||||
Expression.class);
|
||||
assertEquals("payload", expression.getExpressionString());
|
||||
Message<?> message = MessageBuilder.withPayload("foo").build();
|
||||
assertEquals("foo", processor.processMessage(message));
|
||||
|
||||
Method method = JmsOutboundGateway.class.getDeclaredMethod("determineReplyDestination", Message.class, Session.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
Session session = mock(Session.class);
|
||||
Queue queue = mock(Queue.class);
|
||||
when(session.createQueue("foo")).thenReturn(queue);
|
||||
Destination replyQ = (Destination) method.invoke(gateway, message, session);
|
||||
assertSame(queue, replyQ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayWithDestBeanRefExpression() {
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"jmsOutboundGatewayReplyDestOptions.xml", this.getClass());
|
||||
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("jmsGatewayDestExpressionBeanRef");
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(endpoint);
|
||||
JmsOutboundGateway gateway = (JmsOutboundGateway) accessor.getPropertyValue("handler");
|
||||
ExpressionEvaluatingMessageProcessor<?> processor = TestUtils.getPropertyValue(gateway, "replyDestinationExpressionProcessor",
|
||||
ExpressionEvaluatingMessageProcessor.class);
|
||||
Expression expression = TestUtils.getPropertyValue(gateway, "replyDestinationExpressionProcessor.expression",
|
||||
Expression.class);
|
||||
assertEquals("@replyQueue", expression.getExpressionString());
|
||||
assertSame(context.getBean("replyQueue"), processor.processMessage(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayWithDestAndDestExpression() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(
|
||||
"jmsOutboundGatewayReplyDestOptions-fail.xml", this.getClass());
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertTrue(e.getMessage().startsWith("Configuration problem: Only one of the " +
|
||||
"'replyQueue', 'reply-destination-name', or 'reply-destination-expression' attributes is allowed."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gatewayMaintainsReplyChannelAndInboundHistory() {
|
||||
ActiveMqTestUtils.prepare();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xmlns:jms="http://www.springframework.org/schema/integration/jms"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/jms
|
||||
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
|
||||
|
||||
<si:channel id="requestChannel"/>
|
||||
|
||||
<jms:outbound-gateway id="jmsGatewayDest"
|
||||
request-destination="requestQueue"
|
||||
request-channel="requestChannel"
|
||||
reply-destination="replyQueue"
|
||||
reply-destination-expression="'foo'"/>
|
||||
|
||||
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.integration.jms.StubConnection">
|
||||
<constructor-arg value="test-message"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="requestQueue" class="org.springframework.integration.jms.StubQueue"/>
|
||||
|
||||
<bean id="replyQueue" class="org.springframework.integration.jms.StubQueue"/>
|
||||
</beans>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xmlns:jms="http://www.springframework.org/schema/integration/jms"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/jms
|
||||
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
|
||||
|
||||
<si:channel id="requestChannel"/>
|
||||
|
||||
<jms:outbound-gateway id="jmsGatewayDest"
|
||||
request-destination="requestQueue"
|
||||
request-channel="requestChannel"
|
||||
reply-destination="replyQueue"/>
|
||||
|
||||
<jms:outbound-gateway id="jmsGatewayDestName"
|
||||
request-destination="requestQueue"
|
||||
request-channel="requestChannel"
|
||||
reply-destination-name="replyQueueName"/>
|
||||
|
||||
<jms:outbound-gateway id="jmsGatewayDestExpression"
|
||||
request-destination="requestQueue"
|
||||
request-channel="requestChannel"
|
||||
reply-destination-expression="payload"/>
|
||||
|
||||
<jms:outbound-gateway id="jmsGatewayDestExpressionBeanRef"
|
||||
request-destination="requestQueue"
|
||||
request-channel="requestChannel"
|
||||
reply-destination-expression="@replyQueue"/>
|
||||
|
||||
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.integration.jms.StubConnection">
|
||||
<constructor-arg value="test-message"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<bean id="requestQueue" class="org.springframework.integration.jms.StubQueue"/>
|
||||
|
||||
<bean id="replyQueue" class="org.springframework.integration.jms.StubQueue"/>
|
||||
</beans>
|
||||
@@ -19,9 +19,12 @@
|
||||
Whereas the JMS Channel Adapters are intended for unidirectional Messaging (send-only or receive-only), Spring
|
||||
Integration also provides inbound and outbound JMS Gateways for request/reply operations. The inbound gateway
|
||||
relies on one of Spring's MessageListener container implementations for Message-driven reception that is also
|
||||
capable of sending a return value to the "reply-to" Destination as provided by the received Message. The outbound
|
||||
Gateway sends a JMS Message to a "request-destination" and then receives a reply Message. The "reply-destination"
|
||||
reference (or "reply-destination-name") can be configured explicitly or else the outbound gateway will use a
|
||||
capable of sending a return value to the <code>reply-to</code> Destination as provided by the received Message. The outbound
|
||||
Gateway sends a JMS Message to a <code>request-destination</code> (or <code>request-destination-name</code> or
|
||||
<code>request-destination-expression</code>)
|
||||
and then receives a reply Message. The <code>reply-destination</code>
|
||||
reference (or <code>reply-destination-name</code> or <code>reply-destination-expression</code>) can be configured
|
||||
explicitly or else the outbound gateway will use a
|
||||
JMS <ulink url="http://docs.oracle.com/javaee/6/api/javax/jms/TemporaryQueue.html">TemporaryQueue</ulink>.
|
||||
</para>
|
||||
<para>
|
||||
@@ -279,6 +282,224 @@
|
||||
<ulink url="http://static.springsource.org/spring/docs/current/spring-framework-reference/html/jms.html">Spring JMS documentation</ulink>
|
||||
for their meanings.
|
||||
</para>
|
||||
<section>
|
||||
<title>Attribute Reference</title>
|
||||
<programlisting><![CDATA[
|
||||
<int-jms:outbound-gateway
|
||||
connection-factory="connectionFactory"]]><co id="jog010" /><![CDATA[
|
||||
correlation-key=""]]><co id="jog020" /><![CDATA[
|
||||
delivery-persistent=""]]><co id="jog030" /><![CDATA[
|
||||
destination-resolver=""]]><co id="jog040" /><![CDATA[
|
||||
explicit-qos-enabled=""]]><co id="jog050" /><![CDATA[
|
||||
extract-reply-payload="true"]]><co id="jog060" /><![CDATA[
|
||||
extract-request-payload="true"]]><co id="jog070" /><![CDATA[
|
||||
header-mapper=""]]><co id="jog080" /><![CDATA[
|
||||
message-converter=""]]><co id="jog090" /><![CDATA[
|
||||
priority=""]]><co id="jog100" /><![CDATA[
|
||||
receive-timeout=""]]><co id="jog110" /><![CDATA[
|
||||
reply-channel=""]]><co id="jog120" /><![CDATA[
|
||||
reply-destination=""]]><co id="jog130" /><![CDATA[
|
||||
reply-destination-expression=""]]><co id="jog140" /><![CDATA[
|
||||
reply-destination-name=""]]><co id="jog150" /><![CDATA[
|
||||
reply-pub-sub-domain=""]]><co id="jog160" /><![CDATA[
|
||||
reply-timeout=""]]><co id="jog170" /><![CDATA[
|
||||
request-channel=""]]><co id="jog180" /><![CDATA[
|
||||
request-destination=""]]><co id="jog190" /><![CDATA[
|
||||
request-destination-expression=""]]><co id="jog200" /><![CDATA[
|
||||
request-destination-name=""]]><co id="jog210" /><![CDATA[
|
||||
request-pub-sub-domain=""]]><co id="jog220" /><![CDATA[
|
||||
time-to-live="">]]><co id="jog230" /><![CDATA[
|
||||
<int-jms:reply-listener />]]><co id="jog240" /><![CDATA[
|
||||
</int-jms:outbound-gateway>
|
||||
]]></programlisting>
|
||||
<calloutlist>
|
||||
<callout arearefs="jog010">
|
||||
<para>
|
||||
Reference to a <interfacename>javax.jms.ConnectionFactory</interfacename>;
|
||||
default <code>connectionFactory</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog020">
|
||||
<para>
|
||||
The name of a property that will contain correlation data to correlate responses with
|
||||
replies. If omitted, the gateway will expect the responding system to return the
|
||||
value of the outbound JMSMessageID header in the JMSCorrelationID header. If specified,
|
||||
the gateway will generate a correlation id and populate the specified property with
|
||||
it; the responding system must echo back that value in the same property. Can be set
|
||||
to <code>JMSCorrelationID</code>, in which case the standard header is used instead
|
||||
of a simple String property to hold the correlation data. When a <code><reply-container/>
|
||||
</code> is used, the correlation-key MUST be specified if an explicit <code>reply-destination</code>
|
||||
is provided.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog030">
|
||||
<para>
|
||||
A boolean value indicating whether the delivery mode should be
|
||||
DeliveryMode.PERSISTENT (true) or DeliveryMode.NON_PERSISTENT (false).
|
||||
This setting will only take effect if <code>explicit-qos-enabled</code> is <code>true</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog040">
|
||||
<para>
|
||||
A <interfacename>DestinationResolver</interfacename>; default is a
|
||||
<classname>DynamicDestinationResolver</classname> which simply maps the
|
||||
destination name to a queue or topic of that name.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog050">
|
||||
<para>
|
||||
When set to <code>true</code>, enables the use of quality of service attributes -
|
||||
<code>priority</code>, <code>delivery-mode</code>, <code>time-to-live</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog060">
|
||||
<para>
|
||||
When set to <code>true</code> (default), the payload of the Spring Integration reply Message will be
|
||||
created from the JMS Reply Message's body (using the <interfacename>MessageConverter</interfacename>).
|
||||
When set to <code>false</code>, the entire JMS Message will become the payload of the
|
||||
Spring Integration Message.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog070">
|
||||
<para>
|
||||
When set to <code>true</code> (default), the payload of the Spring Integration Message will
|
||||
be converted to a JMSMessage (using the <interfacename>MessageConverter</interfacename>).
|
||||
When set to <code>false</code>, the entire Spring Integration Message will be converted
|
||||
to the the JMSMessage. In both cases, the Spring Integration Message Headers are
|
||||
mapped to JMS headers and properties using the HeaderMapper.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog080">
|
||||
<para>
|
||||
A <interfacename>HeaderMapper</interfacename> used to map Spring Integration Message
|
||||
Headers to/from JMS Message Headers/Properties.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog090">
|
||||
<para>
|
||||
A reference to a <interfacename>MessageConverter</interfacename> for converting between JMS Messages
|
||||
and the Spring Integration Message payloads (or messages if <code>extract-request-payload</code>
|
||||
is <code>false</code>). Default is a <classname>SimpleMessageConverter</classname>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog100">
|
||||
<para>
|
||||
The default priority of request messages. Overridden by the message priority
|
||||
header, if present; range 0-9.
|
||||
This setting will only take effect if <code>explicit-qos-enabled</code> is
|
||||
<code>true</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog110">
|
||||
<para>
|
||||
The time (in millseconds) to wait for a reply. Default 5 seconds.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog120">
|
||||
<para>
|
||||
The channel to which the reply message will be sent.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog130">
|
||||
<para>
|
||||
A reference to a <interfacename>Destination</interfacename> which will be set as
|
||||
the JMSReplyTo header. At most, only one of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is allowed. If none is provided, a <classname>TemporaryQueue</classname> is used
|
||||
for replies to this gateway.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog140">
|
||||
<para>
|
||||
A SpEL expression evaluating to a <interfacename>Destination</interfacename> which will be set as
|
||||
the JMSReplyTo header. The expression can result in a <interfacename>Destination
|
||||
</interfacename> object, or a <classname>String</classname>, which will be used by the
|
||||
<interfacename>DestinationResolver</interfacename> to resolve the actual
|
||||
<interfacename>Destination</interfacename>. At most, only one of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is allowed. If none is provided, a <classname>TemporaryQueue</classname> is used
|
||||
for replies to this gateway.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog150">
|
||||
<para>
|
||||
The name of the destination which will be set as the JMSReplyTo header; used by the
|
||||
<interfacename>DestinationResolver</interfacename> to resolve the actual
|
||||
<interfacename>Destination</interfacename>. At most, only one of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is allowed. If none is provided, a <classname>TemporaryQueue</classname> is used
|
||||
for replies to this gateway.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog160">
|
||||
<para>
|
||||
When set to <code>true</code>, indicates that any reply <interfacename>Destination</interfacename>
|
||||
resolved by the <interfacename>DestinationResolver</interfacename> should be a
|
||||
<interfacename>Topic</interfacename> rather then a <interfacename>Queue</interfacename>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog170">
|
||||
<para>
|
||||
The time the gateway will wait when sending the reply message to the <code>reply-channel</code>.
|
||||
This only has an effect if the <code>reply-channel</code> can block - such as a
|
||||
<classname>QueueChannel</classname> with a capacity limit that is currently full. Default: infinity.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog180">
|
||||
<para>
|
||||
The channel on which this gateway receives request messages.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog190">
|
||||
<para>
|
||||
A reference to a <interfacename>Destination</interfacename> to which request messages
|
||||
will be sent. One, and only one, of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is required.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog200">
|
||||
<para>
|
||||
A SpEL expression evaluating to a <interfacename>Destination</interfacename> to which
|
||||
request messages will be sent. The expression can result in a <interfacename>Destination
|
||||
</interfacename> object, or a <classname>String</classname>, which will be used by the
|
||||
<interfacename>DestinationResolver</interfacename> to resolve the actual
|
||||
<interfacename>Destination</interfacename>. One, and only one, of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is required.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog210">
|
||||
<para>
|
||||
The name of the destination to which request messages will be sent; used by the
|
||||
<interfacename>DestinationResolver</interfacename> to resolve the actual
|
||||
<interfacename>Destination</interfacename>. One, and only one, of <code>reply-destination</code>,
|
||||
<code>reply-destination-expression</code>, or <code>reply-destination-name</code>
|
||||
is required.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog220">
|
||||
<para>
|
||||
When set to <code>true</code>, indicates that any request <interfacename>Destination</interfacename>
|
||||
resolved by the <interfacename>DestinationResolver</interfacename> should be a
|
||||
<interfacename>Topic</interfacename> rather then a <interfacename>Queue</interfacename>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog230">
|
||||
<para>
|
||||
Specify the message time to live.
|
||||
This setting will only take effect if <code>explicit-qos-enabled</code> is <code>true</code>.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="jog240">
|
||||
<para>
|
||||
When this element is included, replies are received by a <interfacename>MessageListenerContainer
|
||||
</interfacename> rather than creating a consumer for each reply. This can be more efficient in
|
||||
many cases.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="jms-header-mapping">
|
||||
|
||||
Reference in New Issue
Block a user