diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java index b81917096a5..bf812fc8bbf 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java @@ -48,6 +48,7 @@ * @author Gary Russell * @author Artem Bilan * @author Ngoc Nhan + * @author Jun Cho * * @since 2.1 */ @@ -95,6 +96,12 @@ public void setExpectReply(boolean expectReply) { * received within the confirm timeout or a negative acknowledgment or returned * message is received, an exception will be thrown. Does not apply to the gateway * since it blocks awaiting the reply. + *
+ * When there is no correlation data for the message and the connection factory is
+ * configured for simple publisher confirms, the message is sent within the scope of a
+ * {@code RabbitTemplate.invoke()} operation and {@code waitForConfirmsOrDie()} is used
+ * instead. A returned message does not fail the send in that case; it is published to
+ * the return channel, if one is configured.
* @param waitForConfirm true to block until the confirmation or timeout is received.
* @since 5.2
* @see #setConfirmTimeout(long)
@@ -156,6 +163,10 @@ protected void endpointInit() {
+ "does not support returned messages; none will be received");
}
}
+ if (this.waitForConfirm && !usesSimplePublisherConfirms() && !usesCorrelatedPublisherConfirms()) {
+ this.logger.warn("The 'waitForConfirm' is set to true but the underlying connection factory "
+ + "does not support publisher confirms; no confirmations will be awaited");
+ }
Duration confirmTimeout = getConfirmTimeout();
if (confirmTimeout != null) {
this.waitForConfirmTimeout = confirmTimeout;
@@ -181,6 +192,10 @@ protected void doStop() {
multiSend(requestMessage, exchangeName, routingKey);
return null;
}
+ else if (this.waitForConfirm && correlationData == null && usesSimplePublisherConfirms()) {
+ sendAndWaitForConfirms(exchangeName, routingKey, requestMessage);
+ return null;
+ }
else {
send(exchangeName, routingKey, requestMessage, correlationData);
if (this.waitForConfirm && correlationData != null) {
@@ -208,6 +223,28 @@ private void multiSend(Message> requestMessage, @Nullable String exchangeName,
});
}
+ private void sendAndWaitForConfirms(@Nullable String exchangeName, @Nullable String routingKey,
+ Message> requestMessage) {
+
+ RabbitTemplate rabbitTemplateToUse = this.rabbitTemplate;
+ Assert.notNull(rabbitTemplateToUse, "The 'RabbitTemplate' must be provided to wait for confirms.");
+ rabbitTemplateToUse.<@Nullable Object>invoke(template -> {
+ doRabbitSend(exchangeName, routingKey, requestMessage, null, rabbitTemplateToUse);
+ template.waitForConfirmsOrDie(this.waitForConfirmTimeout.toMillis());
+ return null;
+ });
+ }
+
+ private boolean usesSimplePublisherConfirms() {
+ return this.rabbitTemplate != null
+ && this.rabbitTemplate.getConnectionFactory().isSimplePublisherConfirms();
+ }
+
+ private boolean usesCorrelatedPublisherConfirms() {
+ return this.rabbitTemplate != null
+ && this.rabbitTemplate.getConnectionFactory().isPublisherConfirms();
+ }
+
private void waitForConfirm(Message> requestMessage, CorrelationData correlationData) {
try {
Confirm confirm = correlationData.getFuture().get(this.waitForConfirmTimeout.toMillis(),
diff --git a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd
index 14163860e77..7d736888c2d 100644
--- a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd
+++ b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp.xsd
@@ -70,6 +70,11 @@
been received. Requires a template configured for returns. If a confirm is not
received within the confirm timeout or a negative acknowledgment or returned
message is received, an exception will be thrown.
+ When there is no correlation data for the message and the connection factory is
+ configured for simple publisher confirms, the message is sent within the scope of
+ a RabbitTemplate.invoke() operation and waitForConfirmsOrDie() is used instead.
+ A returned message does not fail the send in that case; it is published to the
+ return channel, if one is configured.
diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java
index 7f74a3a8942..9d3e52f1077 100644
--- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java
+++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests2.java
@@ -25,6 +25,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.amqp.AmqpException;
+import org.springframework.amqp.AmqpIOException;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.QueueBuilder.Overflow;
@@ -53,6 +54,7 @@
/**
* @author Gary Russell
* @author Artem Bilan
+ * @author Jun Cho
*
* @since 5.2
*
@@ -63,14 +65,18 @@ public class AmqpOutboundEndpointTests2 implements RabbitTestContainer {
static final String QUEUE_TEST_CONFIRM_OK = "testConfirmOk";
+ static final String QUEUE_TEST_SIMPLE_CONFIRMS = "testSimpleConfirms";
+
@BeforeAll
static void initQueue() throws IOException, InterruptedException {
RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + QUEUE_TEST_CONFIRM_OK);
+ RABBITMQ.execInContainer("rabbitmqadmin", "declare", "queue", "name=" + QUEUE_TEST_SIMPLE_CONFIRMS);
}
@AfterAll
static void deleteQueue() throws IOException, InterruptedException {
RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + QUEUE_TEST_CONFIRM_OK);
+ RABBITMQ.execInContainer("rabbitmqadmin", "delete", "queue", "name=" + QUEUE_TEST_SIMPLE_CONFIRMS);
}
@Test
@@ -118,6 +124,31 @@ void testWithReject(@Autowired IntegrationFlow flow, @Autowired RabbitAdmin admi
admin.deleteQueue(queue.getName());
}
+ @Test
+ void simpleConfirmsOk(@Autowired IntegrationFlow simpleConfirmsFlow, @Autowired RabbitTemplate template) {
+ simpleConfirmsFlow.getInputChannel()
+ .send(new GenericMessage<>("test", Collections.singletonMap("rk", QUEUE_TEST_SIMPLE_CONFIRMS)));
+ assertThat(template.receive(QUEUE_TEST_SIMPLE_CONFIRMS)).isNotNull();
+ }
+
+ @Test
+ void simpleConfirmsWithReject(@Autowired IntegrationFlow simpleConfirmsFlow,
+ @Autowired RabbitAdmin simpleConfirmsAdmin) {
+
+ Queue queue = QueueBuilder.nonDurable().autoDelete().maxLength(1L).overflow(Overflow.rejectPublish).build();
+ String queueName = queue.getName();
+ simpleConfirmsAdmin.declareQueue(queue);
+ GenericMessage