From 7aa04d32243ead986c6bd7e4f396321348e6212e Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 11 Sep 2017 12:43:06 -0400 Subject: [PATCH] GH-175: XML Support for Error Handling Resolves spring-projects/spring-integration-kafka#175 __cherry-pick to 2.3.x__ --- ...afkaMessageDrivenChannelAdapterParser.java | 5 +- .../KafkaOutboundChannelAdapterParser.java | 23 +++--- .../config/spring-integration-kafka-3.0.xsd | 71 +++++++++++++++++++ ...rivenChannelAdapterParserTests-context.xml | 10 ++- ...essageDrivenChannelAdapterParserTests.java | 15 +++- ...afkaOutboundAdapterParserTests-context.xml | 9 +++ .../xml/KafkaOutboundAdapterParserTests.java | 10 ++- 7 files changed, 128 insertions(+), 15 deletions(-) diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParser.java index 0db6167370..a8ac83b3ff 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParser.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -54,6 +54,9 @@ public class KafkaMessageDrivenChannelAdapterParser extends AbstractChannelAdapt IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "payload-type"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-message-strategy"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "retry-template"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "recovery-callback"); return builder.getBeanDefinition(); } diff --git a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java index b3638eac92..b59a0c8e68 100644 --- a/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java +++ b/spring-integration-kafka/src/main/java/org/springframework/integration/kafka/config/xml/KafkaOutboundChannelAdapterParser.java @@ -40,50 +40,55 @@ public class KafkaOutboundChannelAdapterParser extends AbstractOutboundChannelAd @Override protected AbstractBeanDefinition parseConsumer(final Element element, final ParserContext parserContext) { - final BeanDefinitionBuilder kafkaProducerMessageHandlerBuilder = + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(KafkaProducerMessageHandler.class); final String kafkaTemplateBeanName = element.getAttribute("kafka-template"); - kafkaProducerMessageHandlerBuilder.addConstructorArgReference(kafkaTemplateBeanName); + builder.addConstructorArgReference(kafkaTemplateBeanName); BeanDefinition topicExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("topic", "topic-expression", parserContext, element, false); if (topicExpressionDef != null) { - kafkaProducerMessageHandlerBuilder.addPropertyValue("topicExpression", topicExpressionDef); + builder.addPropertyValue("topicExpression", topicExpressionDef); } BeanDefinition messageKeyExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("message-key", "message-key-expression", parserContext, element, false); if (messageKeyExpressionDef != null) { - kafkaProducerMessageHandlerBuilder.addPropertyValue("messageKeyExpression", messageKeyExpressionDef); + builder.addPropertyValue("messageKeyExpression", messageKeyExpressionDef); } BeanDefinition partitionIdExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("partition-id", "partition-id-expression", parserContext, element, false); if (partitionIdExpressionDef != null) { - kafkaProducerMessageHandlerBuilder.addPropertyValue("partitionIdExpression", partitionIdExpressionDef); + builder.addPropertyValue("partitionIdExpression", partitionIdExpressionDef); } - IntegrationNamespaceUtils.setValueIfAttributeDefined(kafkaProducerMessageHandlerBuilder, element, "sync"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "sync"); BeanDefinition sendTimeoutExpressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("send-timeout", "send-timeout-expression", parserContext, element, false); if (sendTimeoutExpressionDef != null) { - kafkaProducerMessageHandlerBuilder.addPropertyValue("sendTimeoutExpression", sendTimeoutExpressionDef); + builder.addPropertyValue("sendTimeoutExpression", sendTimeoutExpressionDef); } BeanDefinition timestampExpressionDef = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("timestamp-expression", element); if (timestampExpressionDef != null) { - kafkaProducerMessageHandlerBuilder.addPropertyValue("timestampExpression", timestampExpressionDef); + builder.addPropertyValue("timestampExpression", timestampExpressionDef); } - return kafkaProducerMessageHandlerBuilder.getBeanDefinition(); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-message-strategy"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "send-failure-channel"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "send-success-channel", + "outputChannel"); + + return builder.getBeanDefinition(); } } diff --git a/spring-integration-kafka/src/main/resources/org/springframework/integration/kafka/config/spring-integration-kafka-3.0.xsd b/spring-integration-kafka/src/main/resources/org/springframework/integration/kafka/config/spring-integration-kafka-3.0.xsd index 8116abc983..a50008d7f6 100644 --- a/spring-integration-kafka/src/main/resources/org/springframework/integration/kafka/config/spring-integration-kafka-3.0.xsd +++ b/spring-integration-kafka/src/main/resources/org/springframework/integration/kafka/config/spring-integration-kafka-3.0.xsd @@ -149,6 +149,33 @@ + + + + + + + + + + + + + + + + + + + + + @@ -226,6 +253,35 @@ + + + + A retry template for retrying deliveries; an 'error-channel' is not allowed + when a retry template is provided; configure a 'recovery-callback' such as an + 'ErrorMessageSendingRecoverer' when using a retry template. + + + + + + + + + + + + Used in conjunction with a 'retry-template'; in most cases this will be + an 'ErrorMessageSendingRecoverer'. Omitting this element will cause an + exception to be thrown to the listener container after retries are exhausted. + + + + + + + + + @@ -236,4 +292,19 @@ + + + + + + + + + + + + + diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests-context.xml b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests-context.xml index 101789a50d..460e80dc53 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests-context.xml +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests-context.xml @@ -22,7 +22,9 @@ channel="nullChannel" message-converter="messageConverter" payload-type="java.lang.String" - error-channel="errorChannel" /> + error-message-strategy="ems" + retry-template="retryTemplate" + recovery-callback="recoveryCallback" /> + + + + + + diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests.java index 0f15b47764..020f1cb53b 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaMessageDrivenChannelAdapterParserTests.java @@ -30,6 +30,7 @@ import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter; import org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.ListenerMode; +import org.springframework.integration.support.ErrorMessageStrategy; import org.springframework.integration.test.util.TestUtils; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.listener.KafkaMessageListenerContainer; @@ -37,6 +38,7 @@ import org.springframework.kafka.listener.adapter.FilteringMessageListenerAdapte import org.springframework.kafka.listener.adapter.RecordFilterStrategy; import org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter; import org.springframework.kafka.listener.config.ContainerProperties; +import org.springframework.retry.RecoveryCallback; import org.springframework.retry.support.RetryTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -61,13 +63,21 @@ public class KafkaMessageDrivenChannelAdapterParserTests { @Autowired private KafkaMessageDrivenChannelAdapter kafkaBatchListener; + @Autowired + private ErrorMessageStrategy ems; + + @Autowired + private RetryTemplate retryTemplate; + + @Autowired + private RecoveryCallback recoveryCallback; + @Test public void testKafkaMessageDrivenChannelAdapterParser() throws Exception { assertThat(this.kafkaListener.isAutoStartup()).isFalse(); assertThat(this.kafkaListener.isRunning()).isFalse(); assertThat(this.kafkaListener.getPhase()).isEqualTo(100); assertThat(TestUtils.getPropertyValue(this.kafkaListener, "outputChannel")).isSameAs(this.nullChannel); - assertThat(TestUtils.getPropertyValue(this.kafkaListener, "errorChannel")).isSameAs(this.errorChannel); KafkaMessageListenerContainer container = TestUtils.getPropertyValue(this.kafkaListener, "messageListenerContainer", KafkaMessageListenerContainer.class); @@ -78,6 +88,9 @@ public class KafkaMessageDrivenChannelAdapterParserTests { .isEqualTo(String.class); assertThat(TestUtils.getPropertyValue(this.kafkaListener, "batchListener.fallbackType")) .isEqualTo(String.class); + assertThat(TestUtils.getPropertyValue(this.kafkaListener, "errorMessageStrategy")).isSameAs(this.ems); + assertThat(TestUtils.getPropertyValue(this.kafkaListener, "retryTemplate")).isSameAs(this.retryTemplate); + assertThat(TestUtils.getPropertyValue(this.kafkaListener, "recoveryCallback")).isSameAs(this.recoveryCallback); } @Test diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests-context.xml b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests-context.xml index 6f6b0c2319..42c07fffb9 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests-context.xml +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests-context.xml @@ -20,6 +20,9 @@ partition-id-expression="'2'" send-timeout-expression="1000" timestamp-expression="T(System).currentTimeMillis()" + error-message-strategy="ems" + send-failure-channel="failures" + send-success-channel="successes" > @@ -45,4 +48,10 @@ partition-id="#{0}" send-timeout="500" /> + + + + + + diff --git a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java index 9b418f64e3..4fc614d234 100644 --- a/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java +++ b/spring-integration-kafka/src/test/java/org/springframework/integration/kafka/config/xml/KafkaOutboundAdapterParserTests.java @@ -74,6 +74,13 @@ public class KafkaOutboundAdapterParserTests { assertThat(TestUtils.getPropertyValue(messageHandler, "timestampExpression.expression")) .isEqualTo("T(System).currentTimeMillis()"); + assertThat(TestUtils.getPropertyValue(messageHandler, "errorMessageStrategy")) + .isSameAs(this.appContext.getBean("ems")); + assertThat(TestUtils.getPropertyValue(messageHandler, "sendFailureChannel")) + .isSameAs(this.appContext.getBean("failures")); + assertThat(TestUtils.getPropertyValue(messageHandler, "outputChannel")) + .isSameAs(this.appContext.getBean("successes")); + messageHandler = this.appContext.getBean("kafkaOutboundChannelAdapter2.handler", KafkaProducerMessageHandler.class); assertThat(messageHandler).isNotNull(); @@ -81,14 +88,11 @@ public class KafkaOutboundAdapterParserTests { assertThat(TestUtils.getPropertyValue(messageHandler, "sync", Boolean.class)).isFalse(); assertThat(TestUtils.getPropertyValue(messageHandler, "sendTimeoutExpression.literalValue")).isEqualTo("500"); - - } @Test public void testSyncMode() { - @SuppressWarnings("resource") MockProducer mockProducer = new MockProducer(false, new IntegerSerializer(), new StringSerializer()) {