diff --git a/pom.xml b/pom.xml
index 471dde47d..651155eb9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,19 @@
spring-cloud-starter-stream-kafka
spring-cloud-stream-binder-kafka-test-support
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
spring
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 0ebc238a3..16d89e672 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
@@ -13,19 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.cloud.stream.binder.test.junit.kafka;
+
import java.util.Properties;
-import org.I0Itec.zkclient.ZkClient;
-import org.I0Itec.zkclient.exception.ZkInterruptedException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Rule;
-
-import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
-import org.springframework.util.SocketUtils;
-
import kafka.server.KafkaConfig;
import kafka.server.KafkaServer;
import kafka.utils.SystemTime$;
@@ -33,6 +26,14 @@ import kafka.utils.TestUtils;
import kafka.utils.Utils;
import kafka.utils.ZKStringSerializer$;
import kafka.utils.ZkUtils;
+import org.I0Itec.zkclient.ZkClient;
+import org.I0Itec.zkclient.exception.ZkInterruptedException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Rule;
+
+import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
+import org.springframework.util.SocketUtils;
/**
@@ -200,4 +201,3 @@ public class KafkaTestSupport extends AbstractExternalResourceTestSupport, ExtendedProducerProperties>
+ AbstractMessageChannelBinder,
+ ExtendedProducerProperties, Collection>
implements ExtendedPropertiesBinder,
DisposableBean {
- public static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer();
+ private static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer();
- public static final ThreadFactory DAEMON_THREAD_FACTORY;
+ private static final ThreadFactory DAEMON_THREAD_FACTORY;
static {
CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("kafka-binder-");
@@ -135,8 +120,6 @@ public class KafkaMessageChannelBinder extends
private final KafkaBinderConfigurationProperties configurationProperties;
- private final String[] headersToMap;
-
private RetryOperations metadataRetryOperations;
private final Map> topicsInUse = new HashMap<>();
@@ -152,26 +135,28 @@ public class KafkaMessageChannelBinder extends
private KafkaExtendedBindingProperties extendedBindingProperties = new KafkaExtendedBindingProperties();
public KafkaMessageChannelBinder(KafkaBinderConfigurationProperties configurationProperties) {
+ super(false, headersToMap(configurationProperties));
this.configurationProperties = configurationProperties;
- String[] configuredHeaders = configurationProperties.getHeaders();
- if (ObjectUtils.isEmpty(configuredHeaders)) {
- this.headersToMap = BinderHeaders.STANDARD_HEADERS;
+ }
+
+ private static String[] headersToMap(KafkaBinderConfigurationProperties configurationProperties) {
+ String[] headersToMap;
+ if (ObjectUtils.isEmpty(configurationProperties.getHeaders())) {
+ headersToMap = BinderHeaders.STANDARD_HEADERS;
}
else {
String[] combinedHeadersToMap = Arrays.copyOfRange(BinderHeaders.STANDARD_HEADERS, 0,
- BinderHeaders.STANDARD_HEADERS.length + configuredHeaders.length);
- System.arraycopy(configuredHeaders, 0, combinedHeadersToMap, BinderHeaders.STANDARD_HEADERS.length,
- configuredHeaders.length);
- this.headersToMap = combinedHeadersToMap;
+ BinderHeaders.STANDARD_HEADERS.length + configurationProperties.getHeaders().length);
+ System.arraycopy(configurationProperties.getHeaders(), 0, combinedHeadersToMap,
+ BinderHeaders.STANDARD_HEADERS.length,
+ configurationProperties.getHeaders().length);
+ headersToMap = combinedHeadersToMap;
}
+ return headersToMap;
}
- String getZkAddress() {
- return this.configurationProperties.getZkConnectionString();
- }
-
- public ConnectionFactory getConnectionFactory() {
- return connectionFactory;
+ ConnectionFactory getConnectionFactory() {
+ return this.connectionFactory;
}
public void setProducerListener(ProducerListener producerListener) {
@@ -193,13 +178,13 @@ public class KafkaMessageChannelBinder extends
@Override
public void onInit() throws Exception {
ZookeeperConfiguration configuration = new ZookeeperConfiguration(
- new ZookeeperConnect(configurationProperties.getZkConnectionString()));
- configuration.setBufferSize(configurationProperties.getSocketBufferSize());
- configuration.setMaxWait(configurationProperties.getMaxWait());
+ 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 (metadataRetryOperations == null) {
+ if (this.metadataRetryOperations == null) {
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
@@ -211,22 +196,22 @@ public class KafkaMessageChannelBinder extends
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);
- metadataRetryOperations = retryTemplate;
+ this.metadataRetryOperations = retryTemplate;
}
}
@Override
public void destroy() throws Exception {
- if (dlqProducer != null) {
- dlqProducer.close();
- dlqProducer = null;
+ if (this.dlqProducer != null) {
+ this.dlqProducer.close();
+ this.dlqProducer = null;
}
}
/**
* Allowed chars are ASCII alphanumerics, '.', '_' and '-'.
*/
- public static void validateTopicName(String topicName) {
+ static void validateTopicName(String topicName) {
try {
byte[] utf8 = topicName.getBytes("UTF-8");
for (byte b : utf8) {
@@ -244,12 +229,12 @@ public class KafkaMessageChannelBinder extends
@Override
public KafkaConsumerProperties getExtendedConsumerProperties(String channelName) {
- return extendedBindingProperties.getExtendedConsumerProperties(channelName);
+ return this.extendedBindingProperties.getExtendedConsumerProperties(channelName);
}
@Override
public KafkaProducerProperties getExtendedProducerProperties(String channelName) {
- return extendedBindingProperties.getExtendedProducerProperties(channelName);
+ return this.extendedBindingProperties.getExtendedProducerProperties(channelName);
}
Map> getTopicsInUse() {
@@ -257,183 +242,8 @@ public class KafkaMessageChannelBinder extends
}
@Override
- protected Binding doBindConsumer(String name, String group, MessageChannel inputChannel,
+ protected Collection createConsumerDestinationIfNecessary(String name, String group,
ExtendedConsumerProperties properties) {
- // If the caller provides a consumer group, use it; otherwise an anonymous
- // consumer group
- // is generated each time, such that each anonymous binding will receive all
- // messages.
- // Consumers reset offsets at the latest time by default, which allows them to
- // receive only
- // messages sent after they've been bound. That behavior can be changed with the
- // "resetOffsets" and "startOffset" properties.
- 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;
- // The reference point, if not set explicitly is the latest time for anonymous
- // subscriptions and the
- // earliest time for group subscriptions. This allows the latter to receive
- // messages published before the group
- // has been created.
- long referencePoint = properties.getExtension().getStartOffset() != null
- ? properties.getExtension().getStartOffset().getReferencePoint()
- : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime());
- return createKafkaConsumer(name, inputChannel, properties, consumerGroup, referencePoint);
- }
-
- @Override
- public Binding doBindProducer(String name, MessageChannel moduleOutputChannel,
- ExtendedProducerProperties properties) {
-
- Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
- if (logger.isInfoEnabled()) {
- logger.info("Using kafka topic for outbound: " + name);
- }
-
- validateTopicName(name);
-
- Collection partitions = ensureTopicCreated(name, properties.getPartitionCount());
-
- if (properties.getPartitionCount() < partitions.size()) {
- if (logger.isInfoEnabled()) {
- 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.");
- }
- }
-
- topicsInUse.put(name, partitions);
-
- ProducerMetadata producerMetadata = new ProducerMetadata<>(name, byte[].class, byte[].class,
- BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
- producerMetadata.setSync(properties.getExtension().isSync());
- producerMetadata.setCompressionType(properties.getExtension().getCompressionType());
- producerMetadata.setBatchBytes(properties.getExtension().getBufferSize());
- Properties additionalProps = new Properties();
- additionalProps.put(ProducerConfig.ACKS_CONFIG, String.valueOf(configurationProperties.getRequiredAcks()));
- additionalProps.put(ProducerConfig.LINGER_MS_CONFIG,
- String.valueOf(properties.getExtension().getBatchTimeout()));
- ProducerFactoryBean producerFB = new ProducerFactoryBean<>(producerMetadata,
- configurationProperties.getKafkaConnectionString(), additionalProps);
-
- try {
- final ProducerConfiguration producerConfiguration = new ProducerConfiguration<>(
- producerMetadata, producerFB.getObject());
- producerConfiguration.setProducerListener(producerListener);
-
- MessageHandler handler = new SendingHandler(name, properties, partitions.size(), producerConfiguration);
- EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler) {
-
- @Override
- protected void doStop() {
- super.doStop();
- producerConfiguration.stop();
- }
- };
- consumer.setBeanFactory(this.getBeanFactory());
- consumer.setBeanName("outbound." + name);
- consumer.afterPropertiesSet();
- DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel,
- consumer);
- consumer.start();
- return producerBinding;
- }
- catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates a Kafka topic if needed, or try to increase its partition count to the
- * desired number.
- */
- private Collection ensureTopicCreated(final String topicName, final int partitionCount) {
-
- final ZkClient zkClient = new ZkClient(configurationProperties.getZkConnectionString(),
- configurationProperties.getZkSessionTimeout(), configurationProperties.getZkConnectionTimeout(),
- ZKStringSerializer$.MODULE$);
- try {
- final Properties topicConfig = new Properties();
- TopicMetadata topicMetadata = AdminUtils.fetchTopicMetadataFromZk(topicName, zkClient);
- if (topicMetadata.errorCode() == ErrorMapping.NoError()) {
- // only consider minPartitionCount for resizing if autoAddPartitions is
- // true
- int effectivePartitionCount = configurationProperties.isAutoAddPartitions()
- ? Math.max(configurationProperties.getMinPartitionCount(), partitionCount) : partitionCount;
- if (topicMetadata.partitionsMetadata().size() < effectivePartitionCount) {
- if (configurationProperties.isAutoAddPartitions()) {
- AdminUtils.addPartitions(zkClient, topicName, effectivePartitionCount, null, false,
- new Properties());
- }
- else {
- int topicSize = topicMetadata.partitionsMetadata().size();
- throw new BinderException("The number of expected partitions was: " + partitionCount + ", but "
- + topicSize + (topicSize > 1 ? " have " : " has ") + "been found instead."
- + "Consider either increasing the partition count of the topic or enabling `autoAddPartitions`");
- }
- }
- }
- else if (topicMetadata.errorCode() == ErrorMapping.UnknownTopicOrPartitionCode()) {
- if (configurationProperties.isAutoCreateTopics()) {
- Seq