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:
Gunnar Hillert
2013-02-05 09:51:44 -05:00
7 changed files with 520 additions and 46 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>&lt;reply-container/&gt;
</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">