diff --git a/pom.xml b/pom.xml
index 651155eb9..6c4c210e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,10 +7,11 @@
org.springframework.cloud
spring-cloud-build
- 1.1.1.RELEASE
+ 1.1.2.BUILD-SNAPSHOT
+ 1.7
1.4.0.BUILD-SNAPSHOT
diff --git a/spring-cloud-starter-stream-kafka/src/main/resources/META-INF/spring.provides b/spring-cloud-starter-stream-kafka/src/main/resources/META-INF/spring.provides
index 0aa482658..cc7cb9cc2 100644
--- a/spring-cloud-starter-stream-kafka/src/main/resources/META-INF/spring.provides
+++ b/spring-cloud-starter-stream-kafka/src/main/resources/META-INF/spring.provides
@@ -1 +1 @@
-provides: spring-cloud-stream-binder-kafka
\ No newline at end of file
+provides: spring-cloud-starter-stream-kafka
\ No newline at end of file
diff --git a/spring-cloud-stream-binder-kafka-test-support/src/main/java/org/springframework/cloud/stream/binder/test/junit/kafka/KafkaTestSupport.java b/spring-cloud-stream-binder-kafka-test-support/src/main/java/org/springframework/cloud/stream/binder/test/junit/kafka/KafkaTestSupport.java
index 16d89e672..9184f2467 100644
--- a/spring-cloud-stream-binder-kafka-test-support/src/main/java/org/springframework/cloud/stream/binder/test/junit/kafka/KafkaTestSupport.java
+++ b/spring-cloud-stream-binder-kafka-test-support/src/main/java/org/springframework/cloud/stream/binder/test/junit/kafka/KafkaTestSupport.java
@@ -86,12 +86,12 @@ public class KafkaTestSupport extends AbstractExternalResourceTestSupport
- 0.8.2.2
- 2.6.0
- 1.3.1.BUILD-SNAPSHOT
+ 0.9.0.1
+ 1.0.3.BUILD-SNAPSHOT
+ 2.0.1.BUILD-SNAPSHOT
1.0.0
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-kafka-common
+ ${project.version}
+
org.springframework.boot
spring-boot-configuration-processor
@@ -61,9 +66,20 @@
+
+ org.springframework.kafka
+ spring-kafka
+ ${spring-kafka.version}
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+ ${spring-kafka.version}
+
org.apache.kafka
- kafka_2.10
+ kafka_2.11
org.apache.kafka
@@ -78,29 +94,19 @@
rxjava-math
${rxjava-math.version}
-
- org.apache.curator
- curator-recipes
- test
-
org.apache.kafka
- kafka_2.10
+ kafka_2.11
test
test
-
- org.apache.curator
- curator-test
- test
-
org.apache.kafka
- kafka_2.10
+ kafka_2.11
${kafka.version}
@@ -115,7 +121,7 @@
org.apache.kafka
- kafka_2.10
+ kafka_2.11
test
${kafka.version}
@@ -124,21 +130,6 @@
kafka-clients
${kafka.version}
-
- org.apache.curator
- curator-framework
- ${curator.version}
-
-
- org.apache.curator
- curator-recipes
- ${curator.version}
-
-
- org.apache.curator
- curator-test
- ${curator.version}
-
diff --git a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderHealthIndicator.java b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderHealthIndicator.java
deleted file mode 100644
index c523f0854..000000000
--- a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderHealthIndicator.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2016 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.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.cloud.stream.binder.kafka;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import kafka.cluster.Broker;
-import kafka.utils.ZKStringSerializer$;
-import kafka.utils.ZkUtils$;
-import org.I0Itec.zkclient.ZkClient;
-import scala.collection.JavaConversions;
-import scala.collection.Seq;
-
-import org.springframework.boot.actuate.health.Health;
-import org.springframework.boot.actuate.health.HealthIndicator;
-import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
-import org.springframework.integration.kafka.core.BrokerAddress;
-import org.springframework.integration.kafka.core.Partition;
-
-/**
- * Health indicator for Kafka.
- *
- * @author Ilayaperumal Gopinathan
- */
-public class KafkaBinderHealthIndicator implements HealthIndicator {
-
- private final KafkaMessageChannelBinder binder;
-
- private final KafkaBinderConfigurationProperties configurationProperties;
-
- public KafkaBinderHealthIndicator(KafkaMessageChannelBinder binder,
- KafkaBinderConfigurationProperties configurationProperties) {
- this.binder = binder;
- this.configurationProperties = configurationProperties;
- }
-
- @Override
- public Health health() {
- ZkClient zkClient = null;
- try {
- zkClient = new ZkClient(configurationProperties.getZkConnectionString(),
- configurationProperties.getZkSessionTimeout(),
- configurationProperties.getZkConnectionTimeout(), ZKStringSerializer$.MODULE$);
- Set brokersInClusterSet = new HashSet<>();
- Seq allBrokersInCluster = ZkUtils$.MODULE$.getAllBrokersInCluster(zkClient);
- Collection brokersInCluster = JavaConversions.asJavaCollection(allBrokersInCluster);
- for (Broker broker : brokersInCluster) {
- brokersInClusterSet.add(broker.connectionString());
- }
- Set downMessages = new HashSet<>();
- for (Map.Entry> entry : binder.getTopicsInUse().entrySet()) {
- for (Partition partition : entry.getValue()) {
- BrokerAddress address = binder.getConnectionFactory().getLeader(partition);
- if (!brokersInClusterSet.contains(address.toString())) {
- downMessages.add(address.toString());
- }
- }
- }
- if (downMessages.isEmpty()) {
- return Health.up().build();
- }
- return Health.down().withDetail("Following brokers are down: ", downMessages.toString()).build();
- }
- catch (Exception e) {
- return Health.down(e).build();
- }
- finally {
- if (zkClient != null) {
- try {
- zkClient.close();
- }
- catch (Exception e) {
- // ignore
- }
- }
- }
- }
-}
diff --git a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java
index c52a38221..33976bf83 100644
--- a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java
+++ b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java
@@ -21,13 +21,15 @@ package org.springframework.cloud.stream.binder.kafka;
*/
public class KafkaConsumerProperties {
+ private boolean autoRebalanceEnabled = true;
+
private boolean autoCommitOffset = true;
private Boolean autoCommitOnError;
private boolean resetOffsets;
- private KafkaMessageChannelBinder.StartOffset startOffset;
+ private StartOffset startOffset;
private boolean enableDlq;
@@ -49,11 +51,11 @@ public class KafkaConsumerProperties {
this.resetOffsets = resetOffsets;
}
- public KafkaMessageChannelBinder.StartOffset getStartOffset() {
+ public StartOffset getStartOffset() {
return startOffset;
}
- public void setStartOffset(KafkaMessageChannelBinder.StartOffset startOffset) {
+ public void setStartOffset(StartOffset startOffset) {
this.startOffset = startOffset;
}
@@ -80,4 +82,26 @@ public class KafkaConsumerProperties {
public void setRecoveryInterval(int recoveryInterval) {
this.recoveryInterval = recoveryInterval;
}
+
+ public boolean isAutoRebalanceEnabled() {
+ return autoRebalanceEnabled;
+ }
+
+ public void setAutoRebalanceEnabled(boolean autoRebalanceEnabled) {
+ this.autoRebalanceEnabled = autoRebalanceEnabled;
+ }
+
+ public enum StartOffset {
+ earliest(-2L), latest(-1L);
+
+ private final long referencePoint;
+
+ StartOffset(long referencePoint) {
+ this.referencePoint = referencePoint;
+ }
+
+ public long getReferencePoint() {
+ return referencePoint;
+ }
+ }
}
diff --git a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaExtendedBindingProperties.java b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaExtendedBindingProperties.java
index 637d14a35..25be14982 100644
--- a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaExtendedBindingProperties.java
+++ b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaExtendedBindingProperties.java
@@ -26,12 +26,13 @@ import org.springframework.cloud.stream.binder.ExtendedBindingProperties;
* @author Marius Bogoevici
*/
@ConfigurationProperties("spring.cloud.stream.kafka")
-public class KafkaExtendedBindingProperties implements ExtendedBindingProperties {
+public class KafkaExtendedBindingProperties
+ implements ExtendedBindingProperties {
private Map bindings = new HashMap<>();
public Map getBindings() {
- return bindings;
+ return this.bindings;
}
public void setBindings(Map bindings) {
@@ -40,8 +41,8 @@ public class KafkaExtendedBindingProperties implements ExtendedBindingProperties
@Override
public KafkaConsumerProperties getExtendedConsumerProperties(String channelName) {
- if (bindings.containsKey(channelName) && bindings.get(channelName).getConsumer() != null) {
- return bindings.get(channelName).getConsumer();
+ if (this.bindings.containsKey(channelName) && this.bindings.get(channelName).getConsumer() != null) {
+ return this.bindings.get(channelName).getConsumer();
}
else {
return new KafkaConsumerProperties();
@@ -50,8 +51,8 @@ public class KafkaExtendedBindingProperties implements ExtendedBindingProperties
@Override
public KafkaProducerProperties getExtendedProducerProperties(String channelName) {
- if (bindings.containsKey(channelName) && bindings.get(channelName).getProducer() != null) {
- return bindings.get(channelName).getProducer();
+ if (this.bindings.containsKey(channelName) && this.bindings.get(channelName).getProducer() != null) {
+ return this.bindings.get(channelName).getProducer();
}
else {
return new KafkaProducerProperties();
diff --git a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java
index e4b3b390e..ac1f5b59f 100644
--- a/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java
+++ b/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java
@@ -16,33 +16,32 @@
package org.springframework.cloud.stream.binder.kafka;
-import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
import kafka.admin.AdminUtils;
-import kafka.api.OffsetRequest;
import kafka.api.TopicMetadata;
import kafka.common.ErrorMapping;
-import kafka.serializer.DefaultDecoder;
import kafka.utils.ZKStringSerializer$;
import kafka.utils.ZkUtils;
import org.I0Itec.zkclient.ZkClient;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
+import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.serialization.ByteArrayDeserializer;
import org.apache.kafka.common.serialization.ByteArraySerializer;
+import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.utils.Utils;
import scala.collection.Seq;
@@ -56,37 +55,29 @@ import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
import org.springframework.context.Lifecycle;
+import org.springframework.expression.common.LiteralExpression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.core.MessageProducer;
-import org.springframework.integration.kafka.core.ConnectionFactory;
-import org.springframework.integration.kafka.core.DefaultConnectionFactory;
-import org.springframework.integration.kafka.core.KafkaMessage;
-import org.springframework.integration.kafka.core.Partition;
-import org.springframework.integration.kafka.core.ZookeeperConfiguration;
import org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter;
-import org.springframework.integration.kafka.listener.AcknowledgingMessageListener;
-import org.springframework.integration.kafka.listener.Acknowledgment;
-import org.springframework.integration.kafka.listener.ErrorHandler;
-import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer;
-import org.springframework.integration.kafka.listener.KafkaNativeOffsetManager;
-import org.springframework.integration.kafka.listener.MessageListener;
-import org.springframework.integration.kafka.listener.OffsetManager;
-import org.springframework.integration.kafka.support.KafkaProducerContext;
-import org.springframework.integration.kafka.support.ProducerConfiguration;
-import org.springframework.integration.kafka.support.ProducerFactoryBean;
-import org.springframework.integration.kafka.support.ProducerListener;
-import org.springframework.integration.kafka.support.ProducerMetadata;
-import org.springframework.integration.kafka.support.ZookeeperConnect;
-import org.springframework.messaging.Message;
+import org.springframework.integration.kafka.outbound.KafkaProducerMessageHandler;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+import org.springframework.kafka.listener.ErrorHandler;
+import org.springframework.kafka.listener.config.ContainerProperties;
+import org.springframework.kafka.support.ProducerListener;
+import org.springframework.kafka.support.TopicPartitionInitialOffset;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
-import org.springframework.messaging.MessagingException;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryOperations;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
-import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -104,31 +95,17 @@ import org.springframework.util.StringUtils;
*/
public class KafkaMessageChannelBinder extends
AbstractMessageChannelBinder,
- ExtendedProducerProperties, Collection>
+ ExtendedProducerProperties, Collection>
implements ExtendedPropertiesBinder,
DisposableBean {
- private static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer();
-
- private static final ThreadFactory DAEMON_THREAD_FACTORY;
-
- static {
- CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("kafka-binder-");
- threadFactory.setDaemon(true);
- DAEMON_THREAD_FACTORY = threadFactory;
- }
-
private final KafkaBinderConfigurationProperties configurationProperties;
private RetryOperations metadataRetryOperations;
- private final Map> topicsInUse = new HashMap<>();
+ private final Map> topicsInUse = new HashMap<>();
- // -------- Default values for properties -------
-
- private ConnectionFactory connectionFactory;
-
- private ProducerListener producerListener;
+ private ProducerListener producerListener;
private volatile Producer dlqProducer;
@@ -155,14 +132,6 @@ public class KafkaMessageChannelBinder extends
return headersToMap;
}
- ConnectionFactory getConnectionFactory() {
- return this.connectionFactory;
- }
-
- public void setProducerListener(ProducerListener producerListener) {
- this.producerListener = producerListener;
- }
-
/**
* Retry configuration for operations such as validating topic creation
* @param metadataRetryOperations the retry configuration
@@ -177,13 +146,7 @@ public class KafkaMessageChannelBinder extends
@Override
public void onInit() throws Exception {
- ZookeeperConfiguration configuration = new ZookeeperConfiguration(
- new ZookeeperConnect(this.configurationProperties.getZkConnectionString()));
- configuration.setBufferSize(this.configurationProperties.getSocketBufferSize());
- configuration.setMaxWait(this.configurationProperties.getMaxWait());
- DefaultConnectionFactory defaultConnectionFactory = new DefaultConnectionFactory(configuration);
- defaultConnectionFactory.afterPropertiesSet();
- this.connectionFactory = defaultConnectionFactory;
+
if (this.metadataRetryOperations == null) {
RetryTemplate retryTemplate = new RetryTemplate();
@@ -208,23 +171,12 @@ public class KafkaMessageChannelBinder extends
}
}
- /**
- * Allowed chars are ASCII alphanumerics, '.', '_' and '-'.
- */
- static void validateTopicName(String topicName) {
- try {
- byte[] utf8 = topicName.getBytes("UTF-8");
- for (byte b : utf8) {
- if (!((b >= 'a') && (b <= 'z') || (b >= 'A') && (b <= 'Z') || (b >= '0') && (b <= '9') || (b == '.')
- || (b == '-') || (b == '_'))) {
- throw new IllegalArgumentException(
- "Topic name can only have ASCII alphanumerics, '.', '_' and '-'");
- }
- }
- }
- catch (UnsupportedEncodingException e) {
- throw new AssertionError(e); // Can't happen
- }
+ public void setProducerListener(ProducerListener producerListener) {
+ this.producerListener = producerListener;
+ }
+
+ Map> getTopicsInUse() {
+ return this.topicsInUse;
}
@Override
@@ -237,30 +189,89 @@ public class KafkaMessageChannelBinder extends
return this.extendedBindingProperties.getExtendedProducerProperties(channelName);
}
- Map> getTopicsInUse() {
- return this.topicsInUse;
+ @Override
+ protected MessageHandler createProducerMessageHandler(final String name,
+ ExtendedProducerProperties producerProperties) throws Exception {
+
+ KafkaTopicUtils.validateTopicName(name);
+
+ Collection partitions = ensureTopicCreated(name, producerProperties.getPartitionCount());
+
+ if (producerProperties.getPartitionCount() < partitions.size()) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("The `partitionCount` of the producer for topic " + name + " is "
+ + producerProperties.getPartitionCount() + ", smaller than the actual partition count of "
+ + partitions.size() + " of the topic. The larger number will be used instead.");
+ }
+ }
+
+ this.topicsInUse.put(name, partitions);
+
+ ProducerFactory producerFB = getProducerFactory(producerProperties);
+ KafkaTemplate kafkaTemplate = new KafkaTemplate<>(producerFB);
+ if (this.producerListener != null) {
+ kafkaTemplate.setProducerListener(this.producerListener);
+ }
+ return new ProducerConfigurationMessageHandler(kafkaTemplate, name, producerProperties);
}
@Override
- protected Collection createConsumerDestinationIfNecessary(String name, String group,
+ protected void createProducerDestinationIfNecessary(String name,
+ ExtendedProducerProperties properties) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("Using kafka topic for outbound: " + name);
+ }
+ KafkaTopicUtils.validateTopicName(name);
+ Collection partitions = ensureTopicCreated(name, properties.getPartitionCount());
+ if (properties.getPartitionCount() < partitions.size()) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("The `partitionCount` of the producer for topic " + name + " is "
+ + properties.getPartitionCount() + ", smaller than the actual partition count of "
+ + partitions.size() + " of the topic. The larger number will be used instead.");
+ }
+ }
+ this.topicsInUse.put(name, partitions);
+ }
+
+ private ProducerFactory getProducerFactory(
+ ExtendedProducerProperties producerProperties) {
+ Map props = new HashMap<>();
+ props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.configurationProperties.getKafkaConnectionString());
+ props.put(ProducerConfig.RETRIES_CONFIG, 0);
+ props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
+ props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
+ props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
+ props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
+ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class);
+ props.put(ProducerConfig.ACKS_CONFIG, String.valueOf(this.configurationProperties.getRequiredAcks()));
+ props.put(ProducerConfig.LINGER_MS_CONFIG,
+ String.valueOf(producerProperties.getExtension().getBatchTimeout()));
+ props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,
+ producerProperties.getExtension().getCompressionType().toString());
+
+ return new DefaultKafkaProducerFactory<>(props);
+ }
+
+ @Override
+ protected Collection createConsumerDestinationIfNecessary(String name, String group,
ExtendedConsumerProperties properties) {
- validateTopicName(name);
+ KafkaTopicUtils.validateTopicName(name);
if (properties.getInstanceCount() == 0) {
throw new IllegalArgumentException("Instance count cannot be zero");
}
- Collection allPartitions = ensureTopicCreated(name,
+ Collection allPartitions = ensureTopicCreated(name,
properties.getInstanceCount() * properties.getConcurrency());
- Collection listenedPartitions;
+ Collection listenedPartitions;
if (properties.getInstanceCount() == 1) {
listenedPartitions = allPartitions;
}
else {
listenedPartitions = new ArrayList<>();
- for (Partition partition : allPartitions) {
+ for (PartitionInfo partition : allPartitions) {
// divide partitions across modules
- if ((partition.getId() % properties.getInstanceCount()) == properties.getInstanceIndex()) {
+ if ((partition.partition() % properties.getInstanceCount()) == properties.getInstanceIndex()) {
listenedPartitions.add(partition);
}
}
@@ -269,135 +280,73 @@ public class KafkaMessageChannelBinder extends
return listenedPartitions;
}
-
@Override
@SuppressWarnings("unchecked")
- protected MessageProducer createConsumerEndpoint(String name, String group, Collection destination,
+ protected MessageProducer createConsumerEndpoint(String name, String group, Collection destination,
ExtendedConsumerProperties properties) {
-
- Assert.isTrue(!CollectionUtils.isEmpty(destination), "A list of partitions must be provided");
-
- int concurrency = Math.min(properties.getConcurrency(), destination.size());
-
- final ExecutorService dispatcherTaskExecutor =
- Executors.newFixedThreadPool(concurrency, DAEMON_THREAD_FACTORY);
- final KafkaMessageListenerContainer messageListenerContainer = new KafkaMessageListenerContainer(
- this.connectionFactory, destination.toArray(new Partition[destination.size()])) {
-
- @Override
- public void stop(Runnable callback) {
- super.stop(callback);
- if (getOffsetManager() instanceof DisposableBean) {
- try {
- ((DisposableBean) getOffsetManager()).destroy();
- }
- catch (Exception e) {
- KafkaMessageChannelBinder.this.logger.error("Error while closing the offset manager", e);
- }
- }
- dispatcherTaskExecutor.shutdown();
- }
- };
-
- if (this.logger.isDebugEnabled()) {
- this.logger.debug(
- "Listened partitions: " + StringUtils.collectionToCommaDelimitedString(destination));
- }
-
boolean anonymous = !StringUtils.hasText(group);
Assert.isTrue(!anonymous || !properties.getExtension().isEnableDlq(),
"DLQ support is not available for anonymous subscriptions");
String consumerGroup = anonymous ? "anonymous." + UUID.randomUUID().toString() : group;
- long referencePoint = properties.getExtension().getStartOffset() != null
- ? properties.getExtension().getStartOffset().getReferencePoint()
- : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime());
- OffsetManager offsetManager = createOffsetManager(consumerGroup, referencePoint);
- if (properties.getExtension().isResetOffsets()) {
- offsetManager.resetOffsets(destination);
- }
- messageListenerContainer.setOffsetManager(offsetManager);
- messageListenerContainer.setQueueSize(this.configurationProperties.getQueueSize());
- messageListenerContainer.setMaxFetch(this.configurationProperties.getFetchSize());
- boolean autoCommitOnError = properties.getExtension().getAutoCommitOnError() != null
- ? properties.getExtension().getAutoCommitOnError()
- : properties.getExtension().isAutoCommitOffset() && properties.getExtension().isEnableDlq();
- messageListenerContainer.setAutoCommitOnError(autoCommitOnError);
- messageListenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
+ Map props = getConsumerConfig(anonymous, consumerGroup);
+ Deserializer valueDecoder = new ByteArrayDeserializer();
+ Deserializer keyDecoder = new ByteArrayDeserializer();
+
+ ConsumerFactory consumerFactory = new DefaultKafkaConsumerFactory<>(props, keyDecoder,
+ valueDecoder);
+
+ Collection listenedPartitions = (Collection) destination;
+ Assert.isTrue(!CollectionUtils.isEmpty(listenedPartitions), "A list of partitions must be provided");
+ final TopicPartitionInitialOffset[] topicPartitionInitialOffsets = getTopicPartitionInitialOffsets(
+ listenedPartitions);
+
+ final ContainerProperties containerProperties =
+ anonymous || properties.getExtension().isAutoRebalanceEnabled() ? new ContainerProperties(name)
+ : new ContainerProperties(topicPartitionInitialOffsets);
+
+ int concurrency = Math.min(properties.getConcurrency(), listenedPartitions.size());
+ final ConcurrentMessageListenerContainer messageListenerContainer =
+ new ConcurrentMessageListenerContainer(
+ consumerFactory, containerProperties) {
+
+ @Override
+ public void stop(Runnable callback) {
+ super.stop(callback);
+ }
+ };
messageListenerContainer.setConcurrency(concurrency);
- messageListenerContainer.setDispatcherTaskExecutor(dispatcherTaskExecutor);
- final KafkaMessageDrivenChannelAdapter kafkaMessageDrivenChannelAdapter = new KafkaMessageDrivenChannelAdapter(
- messageListenerContainer);
- kafkaMessageDrivenChannelAdapter.setBeanFactory(this.getBeanFactory());
- kafkaMessageDrivenChannelAdapter.setKeyDecoder(new DefaultDecoder(null));
- kafkaMessageDrivenChannelAdapter.setPayloadDecoder(new DefaultDecoder(null));
- kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(properties.getExtension().isAutoCommitOffset());
- kafkaMessageDrivenChannelAdapter.afterPropertiesSet();
- if (properties.getMaxAttempts() > 1) {
- // we need to wrap the adapter listener into a retrying listener so that the retry
- // logic is applied before the ErrorHandler is executed
- final RetryTemplate retryTemplate = buildRetryTemplate(properties);
- if (properties.getExtension().isAutoCommitOffset()) {
- final MessageListener originalMessageListener = (MessageListener) messageListenerContainer
- .getMessageListener();
- messageListenerContainer.setMessageListener(new MessageListener() {
+ messageListenerContainer.getContainerProperties().setAckOnError(isAutoCommitOnError(properties));
- @Override
- public void onMessage(final KafkaMessage message) {
- try {
- retryTemplate.execute(new RetryCallback