INT-2791 temp-channel-transacted for AMQP Channel
JIRA: https://jira.spring.io/browse/INT-2791 * Add `template-channel-transacted` attribute for the `<amqp:channel>`s to separate configuration for container and RabbitTemplate * Make container's `channelTransacted` as `false` by default * Upgrade to `S-AMQP-1.4.0` * Add `AmqpHeaders.PUBLISH_CONFIRM_NACK_CAUSE` according to the new `RabbitTemplate.ConfirmCallback#confirm` signature Conflicts: src/reference/docbook/whats-new.xml Conflicts: src/reference/docbook/whats-new.xml INT-2791: Polishing Upgrade to S-AMQP-2.0 Fix for mock publisher confirm/return tests, since `channel` is now physically closed, if the `ConnectionFactory` isn't `CachingConnectionFactory`
This commit is contained in:
committed by
Gary Russell
parent
d0cf1a8080
commit
d43b89dedc
@@ -112,7 +112,7 @@ subprojects { subproject ->
|
||||
slf4jVersion = "1.7.6"
|
||||
smack3Version = '3.2.1'
|
||||
smackVersion = '4.0.0'
|
||||
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '1.3.5.RELEASE'
|
||||
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '2.0.0.BUILD-SNAPSHOT'
|
||||
springDataMongoVersion = '1.5.0.RELEASE'
|
||||
springDataRedisVersion = '1.3.0.RELEASE'
|
||||
springGemfireVersion = '1.4.0.RELEASE'
|
||||
|
||||
@@ -79,6 +79,8 @@ public abstract class AmqpHeaders {
|
||||
|
||||
public static final String PUBLISH_CONFIRM = PREFIX + "publishConfirm";
|
||||
|
||||
public static final String PUBLISH_CONFIRM_NACK_CAUSE = PREFIX + "publishConfirmNackCause";
|
||||
|
||||
public static final String RETURN_REPLY_CODE = PREFIX + "returnReplyCode";
|
||||
|
||||
public static final String RETURN_REPLY_TEXT = PREFIX + "returnReplyText";
|
||||
|
||||
@@ -109,10 +109,7 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
|
||||
|
||||
private volatile AcknowledgeMode acknowledgeMode;
|
||||
|
||||
/**
|
||||
* This value differs from the container implementations' default (which is false).
|
||||
*/
|
||||
private volatile boolean channelTransacted = true;
|
||||
private volatile boolean channelTransacted;
|
||||
|
||||
private volatile Executor taskExecutor;
|
||||
|
||||
@@ -202,15 +199,21 @@ public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChan
|
||||
}
|
||||
}
|
||||
|
||||
public void setTemplateChannelTransacted(boolean channelTransacted) {
|
||||
if (this.amqpTemplate instanceof RabbitTemplate) {
|
||||
((RabbitTemplate) this.amqpTemplate).setChannelTransacted(channelTransacted);
|
||||
}
|
||||
else if (logger.isInfoEnabled()) {
|
||||
logger.info("AmqpTemplate is not a RabbitTemplate, so configured 'channelTransacted' will be ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Template and Container properties
|
||||
*/
|
||||
|
||||
public void setChannelTransacted(boolean channelTransacted) {
|
||||
this.channelTransacted = channelTransacted;
|
||||
if (this.amqpTemplate instanceof RabbitTemplate) {
|
||||
((RabbitTemplate) this.amqpTemplate).setChannelTransacted(channelTransacted);
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
|
||||
@@ -56,6 +56,7 @@ public class AmqpChannelParser extends AbstractChannelParser {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "amqp-admin");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "channel-transacted");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "template-channel-transacted");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "concurrent-consumers");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encoding");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-handler");
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
package org.springframework.integration.amqp.outbound;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.amqp.AmqpException;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Adapter that converts and sends Messages to an AMQP Exchange.
|
||||
@@ -318,13 +320,20 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm(CorrelationData correlationData, boolean ack) {
|
||||
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
|
||||
Object userCorrelationData = correlationData;
|
||||
if (correlationData instanceof CorrelationDataWrapper) {
|
||||
userCorrelationData = ((CorrelationDataWrapper) correlationData).getUserData();
|
||||
}
|
||||
|
||||
Map<String, Object> headers = new HashMap<String, Object>();
|
||||
headers.put(AmqpHeaders.PUBLISH_CONFIRM, ack);
|
||||
if (!ack && StringUtils.hasText(cause)) {
|
||||
headers.put(AmqpHeaders.PUBLISH_CONFIRM_NACK_CAUSE, cause);
|
||||
}
|
||||
|
||||
Message<Object> confirmMessage = this.getMessageBuilderFactory().withPayload(userCorrelationData)
|
||||
.setHeader(AmqpHeaders.PUBLISH_CONFIRM, ack)
|
||||
.copyHeaders(headers)
|
||||
.build();
|
||||
if (ack && this.confirmAckChannel != null) {
|
||||
this.confirmAckChannel.send(confirmMessage);
|
||||
|
||||
@@ -621,13 +621,6 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:attributeGroup name="containerAndTemplateSharedAttributes">
|
||||
<xsd:attribute name="channel-transacted" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Flag to indicate that channels created by this component will be transactional.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="connection-factory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -663,6 +656,14 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
|
||||
SimpleMessageListenerContainer, such as channelTransacted, connectionFactory, and messagePropertiesConverter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="template-channel-transacted" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Flag to indicate that channels created by this component will be transactional.
|
||||
Only applies to messages sent to this channel, or when 'message-driven' is 'false'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="encoding" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -693,6 +694,14 @@ standard headers to also be mapped. To map all non-standard headers the 'NON_STA
|
||||
connectionFactory, and messsagePropertiesConverter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="channel-transacted" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Flag to indicate that channels created by this component will be transactional.
|
||||
Only applies to outbound messages when 'message-driven' is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="acknowledge-mode">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
<bean id="rabbitConnectionFactory" class="org.springframework.integration.amqp.StubRabbitConnectionFactory"/>
|
||||
|
||||
<amqp:channel id="channelWithSubscriberLimit" max-subscribers="1" missing-queues-fatal="false" />
|
||||
<amqp:channel id="channelWithSubscriberLimit" max-subscribers="1" missing-queues-fatal="false"
|
||||
template-channel-transacted="true"/>
|
||||
|
||||
<amqp:publish-subscribe-channel id="pubSub" />
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ public class AmqpChannelParserTests {
|
||||
assertSame(mbf, TestUtils.getPropertyValue(channel, "dispatcher.messageBuilderFactory"));
|
||||
assertSame(mbf, TestUtils.getPropertyValue(channel, "container.messageListener.messageBuilderFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(channel, "container.missingQueuesFatal", Boolean.class));
|
||||
assertFalse(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class));
|
||||
assertFalse(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,6 +70,8 @@ public class AmqpChannelParserTests {
|
||||
assertEquals(1, TestUtils.getPropertyValue(
|
||||
TestUtils.getPropertyValue(channel, "dispatcher"), "maxSubscribers", Integer.class).intValue());
|
||||
assertFalse(TestUtils.getPropertyValue(channel, "container.missingQueuesFatal", Boolean.class));
|
||||
assertFalse(TestUtils.getPropertyValue(channel, "container.transactional", Boolean.class));
|
||||
assertTrue(TestUtils.getPropertyValue(channel, "amqpTemplate.transactional", Boolean.class));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -38,11 +39,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.rabbitmq.client.AMQP.BasicProperties;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.internal.stubbing.answers.DoesNothing;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@@ -82,9 +86,6 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.rabbitmq.client.AMQP.BasicProperties;
|
||||
import com.rabbitmq.client.Channel;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -186,7 +187,8 @@ public class AmqpOutboundChannelAdapterParserTests {
|
||||
Channel mockChannel = mock(Channel.class);
|
||||
|
||||
when(connectionFactory.createConnection()).thenReturn(mockConnection);
|
||||
PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel);
|
||||
PublisherCallbackChannelImpl publisherCallbackChannel = spy(new PublisherCallbackChannelImpl(mockChannel));
|
||||
doAnswer(new DoesNothing()).when(publisherCallbackChannel).close();
|
||||
when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel);
|
||||
|
||||
MessageChannel requestChannel = context.getBean("pcRequestChannel", MessageChannel.class);
|
||||
@@ -246,7 +248,8 @@ public class AmqpOutboundChannelAdapterParserTests {
|
||||
Channel mockChannel = mock(Channel.class);
|
||||
|
||||
when(connectionFactory.createConnection()).thenReturn(mockConnection);
|
||||
PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel);
|
||||
PublisherCallbackChannelImpl publisherCallbackChannel = spy(new PublisherCallbackChannelImpl(mockChannel));
|
||||
doAnswer(new DoesNothing()).when(publisherCallbackChannel).close();
|
||||
when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel);
|
||||
|
||||
MessageChannel requestChannel = context.getBean("returnRequestChannel", MessageChannel.class);
|
||||
|
||||
@@ -489,6 +489,11 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann
|
||||
defined by this expression and the message will have a header 'amqp_publishConfirm' set to
|
||||
true (ack) or false (nack). Examples: "headers['myCorrelationData']", "payload".
|
||||
<emphasis>Optional</emphasis>.</para>
|
||||
<para>
|
||||
Starting with <emphasis>version 4.1</emphasis> the new <code>amqp_publishConfirmNackCause</code>
|
||||
message header has been added. It contains a <code>cause</code> message of 'nack' for publisher
|
||||
confirm.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="amqp-outbound-channel-adapter-xml-9-co" id="amqp-outbound-channel-adapter-xml-9">
|
||||
<para>The channel to which positive (ack) publisher confirms are sent; payload is
|
||||
@@ -671,7 +676,7 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann
|
||||
</important>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section id="amqp-channels">
|
||||
<title>AMQP Backed Message Channels</title>
|
||||
|
||||
<para>
|
||||
@@ -697,6 +702,14 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann
|
||||
and bind that to the Fanout Exchange while registering a consumer on that Queue to receive Messages. There is no
|
||||
"pollable" option for a publish-subscribe-channel; it must be message-driven.
|
||||
</para>
|
||||
<para>
|
||||
Starting with <emphasis>version 4.1</emphasis> AMQP Backed Message Channels, alongside with
|
||||
<code>channel-transacted</code>, support <code>template-channel-transacted</code> to separate
|
||||
<code>transactional</code> configuration for the <classname>AbstractMessageListenerContainer</classname>
|
||||
and for the <classname>RabbitTemplate</classname>.
|
||||
Note, previously, the <code>channel-transacted</code> was <code>true</code> by default, now it changed to
|
||||
<code>false</code> as standard default value for the <classname>AbstractMessageListenerContainer</classname>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="amqp-message-headers">
|
||||
<title>AMQP Message Headers</title>
|
||||
@@ -760,6 +773,7 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann
|
||||
<listitem>amqp_springReplyCorrelation</listitem>
|
||||
<listitem>amqp_springReplyToStack</listitem>
|
||||
<listitem>amqp_publishConfirm</listitem>
|
||||
<listitem>amqp_publishConfirmNackCause</listitem>
|
||||
<listitem>amqp_returnReplyCode</listitem>
|
||||
<listitem>amqp_returnReplyText</listitem>
|
||||
<listitem>amqp_returnExchange</listitem>
|
||||
|
||||
@@ -154,5 +154,13 @@
|
||||
See <xref linkend="amqp-message-headers"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.1-amqp-channels">
|
||||
<title>AMQP Channels: template-channel-transacted</title>
|
||||
<para>
|
||||
The new <code>template-channel-transacted</code> attribute has been introduced for AMQP
|
||||
<classname>MessageChannel</classname>s.
|
||||
See <xref linkend="amqp-channels"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user