Typesafe consumer and producer configurations
- Change the Binder interface to support ConsumerProperties/ProducerProperties beans and subclasses - Binders can subclass the property beans to add new supported properties that will be automatically populated - Spring Cloud Stream will infer the target type and populate the beans from the environment based on a `spring.cloud.stream.bindings..<bindingName>` prefix - Remove binder defaults and retain only general binder configurations TODO: a) decide on instanceIndex/partitionIndex alignment (we do not need both) b) support `defaultProducer`/`defaultConsumer` properties c) add leniency control on binding (fail/ignore for unknown properties) d) add a `requiredProperties` configuration for consumer/producer properties to finely tune the mandatory properties expected to be supported by a bound application Changes made during review: - Add support for consumer and producer defaults - Remove partitionIndex, keeping only instanceIndex - Fix default properties for Kafka binder - Move batching properties to Rabbit only
This commit is contained in:
committed by
Mark Fisher
parent
6bbf688d63
commit
9b0c4bd627
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class KafkaConsumerProperties extends ConsumerProperties {
|
||||
|
||||
private int minPartitionCount = 1;
|
||||
|
||||
private boolean autoCommitOffset = true;
|
||||
|
||||
private boolean resetOffsets = false;
|
||||
|
||||
private KafkaMessageChannelBinder.StartOffset startOffset = null;
|
||||
|
||||
private KafkaMessageChannelBinder.Mode mode = KafkaMessageChannelBinder.Mode.embeddedHeaders;
|
||||
|
||||
public void setMinPartitionCount(int minPartitionCount) {
|
||||
this.minPartitionCount = minPartitionCount;
|
||||
}
|
||||
|
||||
public int getMinPartitionCount() {
|
||||
return minPartitionCount;
|
||||
}
|
||||
|
||||
public boolean isAutoCommitOffset() {
|
||||
return autoCommitOffset;
|
||||
}
|
||||
|
||||
public void setAutoCommitOffset(boolean autoCommitOffset) {
|
||||
this.autoCommitOffset = autoCommitOffset;
|
||||
}
|
||||
|
||||
public KafkaMessageChannelBinder.Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(KafkaMessageChannelBinder.Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public boolean isResetOffsets() {
|
||||
return resetOffsets;
|
||||
}
|
||||
|
||||
public void setResetOffsets(boolean resetOffsets) {
|
||||
this.resetOffsets = resetOffsets;
|
||||
}
|
||||
|
||||
public KafkaMessageChannelBinder.StartOffset getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public void setStartOffset(KafkaMessageChannelBinder.StartOffset startOffset) {
|
||||
this.startOffset = startOffset;
|
||||
}
|
||||
}
|
||||
@@ -22,18 +22,23 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.OffsetRequest;
|
||||
import kafka.serializer.Decoder;
|
||||
import kafka.serializer.DefaultDecoder;
|
||||
import kafka.utils.ZKStringSerializer$;
|
||||
import kafka.utils.ZkUtils;
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.ByteArraySerializer;
|
||||
import scala.collection.Seq;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
@@ -41,10 +46,8 @@ import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderException;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
import org.springframework.cloud.stream.binder.PartitionHandler;
|
||||
@@ -84,14 +87,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.OffsetRequest;
|
||||
import kafka.serializer.Decoder;
|
||||
import kafka.serializer.DefaultDecoder;
|
||||
import kafka.utils.ZKStringSerializer$;
|
||||
import kafka.utils.ZkUtils;
|
||||
import scala.collection.Seq;
|
||||
|
||||
/**
|
||||
* A {@link Binder} that uses Kafka as the underlying middleware.
|
||||
*
|
||||
@@ -103,79 +98,10 @@ import scala.collection.Seq;
|
||||
* @author Mark Fisher
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel, KafkaConsumerProperties, KafkaProducerProperties> {
|
||||
|
||||
public static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer();
|
||||
|
||||
public static final int METADATA_VERIFICATION_RETRY_ATTEMPTS = 10;
|
||||
|
||||
public static final double METADATA_VERIFICATION_RETRY_BACKOFF_MULTIPLIER = 2;
|
||||
|
||||
public static final int METADATA_VERIFICATION_RETRY_INITIAL_INTERVAL = 100;
|
||||
|
||||
public static final int METADATA_VERIFICATION_MAX_INTERVAL = 1000;
|
||||
|
||||
public static final String FETCH_SIZE = "fetchSize";
|
||||
|
||||
public static final String QUEUE_SIZE = "fetchSize";
|
||||
|
||||
public static final String REQUIRED_ACKS = "requiredAcks";
|
||||
|
||||
public static final String COMPRESSION_CODEC = "compressionCodec";
|
||||
|
||||
public static final String AUTO_COMMIT_ENABLED = "autoCommitEnabled";
|
||||
|
||||
private static final String DEFAULT_COMPRESSION_CODEC = "none";
|
||||
|
||||
private static final int DEFAULT_REQUIRED_ACKS = 1;
|
||||
|
||||
private static final boolean DEFAULT_AUTO_COMMIT_ENABLED = true;
|
||||
|
||||
private static final boolean DEFAULT_RESET_OFFSETS = false;
|
||||
|
||||
private static final int DEFAULT_ZK_SESSION_TIMEOUT = 10000;
|
||||
|
||||
private static final int DEFAULT_ZK_CONNECTION_TIMEOUT = 10000;
|
||||
|
||||
private static final boolean DEFAULT_SYNC_PRODUCER = false;
|
||||
|
||||
protected static final Set<Object> PRODUCER_COMPRESSION_PROPERTIES = new HashSet<Object>(
|
||||
Arrays.asList(new String[] {
|
||||
KafkaMessageChannelBinder.COMPRESSION_CODEC,
|
||||
}));
|
||||
|
||||
private static final Set<Object> KAFKA_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.add(BinderPropertyKeys.MIN_PARTITION_COUNT)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + concurrency + partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(KAFKA_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX) // Not actually used
|
||||
.add(BinderPropertyKeys.COUNT) // Not actually used
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(FETCH_SIZE)
|
||||
.build();
|
||||
|
||||
private static final Set<Object> KAFKA_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.add(BinderPropertyKeys.MIN_PARTITION_COUNT)
|
||||
.add(BinderPropertyKeys.REQUIRED_GROUPS)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Partitioning + kafka producer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.addAll(KAFKA_PRODUCER_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_COMPRESSION_PROPERTIES)
|
||||
.build();
|
||||
|
||||
private RetryOperations retryOperations;
|
||||
|
||||
private final Map<String, Collection<Partition>> topicsInUse = new HashMap<>();
|
||||
@@ -193,26 +119,20 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
// -------- Default values for properties -------
|
||||
|
||||
private int defaultReplicationFactor = 1;
|
||||
private int replicationFactor = 1;
|
||||
|
||||
private String defaultCompressionCodec = DEFAULT_COMPRESSION_CODEC;
|
||||
private int requiredAcks = 1;
|
||||
|
||||
private int defaultRequiredAcks = DEFAULT_REQUIRED_ACKS;
|
||||
private int queueSize = 1024;
|
||||
|
||||
private int defaultQueueSize = 1024;
|
||||
private int maxWait = 100;
|
||||
|
||||
private int defaultMaxWait = 100;
|
||||
|
||||
private int defaultFetchSize = 1024 * 1024;
|
||||
private int fetchSize = 1024 * 1024;
|
||||
|
||||
private int defaultMinPartitionCount = 1;
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
// auto commit property
|
||||
|
||||
private boolean defaultAutoCommitEnabled = DEFAULT_AUTO_COMMIT_ENABLED;
|
||||
|
||||
private int socketBufferSize = 2097152;
|
||||
|
||||
private int offsetUpdateTimeWindow = 10000;
|
||||
@@ -221,22 +141,14 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private int offsetUpdateShutdownTimeout = 2000;
|
||||
|
||||
private Mode mode = Mode.embeddedHeaders;
|
||||
private int zkSessionTimeout = 10000;
|
||||
|
||||
private boolean resetOffsets = DEFAULT_RESET_OFFSETS;
|
||||
|
||||
private StartOffset startOffset = null;
|
||||
|
||||
private int zkSessionTimeout = DEFAULT_ZK_SESSION_TIMEOUT;
|
||||
|
||||
private int zkConnectionTimeout = DEFAULT_ZK_CONNECTION_TIMEOUT;
|
||||
|
||||
private boolean syncProducer = DEFAULT_SYNC_PRODUCER;
|
||||
private int zkConnectionTimeout = 10000;
|
||||
|
||||
private ProducerListener producerListener;
|
||||
|
||||
public KafkaMessageChannelBinder(ZookeeperConnect zookeeperConnect, String brokers, String zkAddress,
|
||||
String... headersToMap) {
|
||||
String... headersToMap) {
|
||||
this.zookeeperConnect = zookeeperConnect;
|
||||
this.brokers = brokers;
|
||||
this.zkAddress = zkAddress;
|
||||
@@ -273,14 +185,6 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
this.offsetUpdateShutdownTimeout = offsetUpdateShutdownTimeout;
|
||||
}
|
||||
|
||||
public boolean isSyncProducer() {
|
||||
return this.syncProducer;
|
||||
}
|
||||
|
||||
public void setSyncProducer(boolean syncProducer) {
|
||||
this.syncProducer = syncProducer;
|
||||
}
|
||||
|
||||
public ConnectionFactory getConnectionFactory() {
|
||||
return connectionFactory;
|
||||
}
|
||||
@@ -301,7 +205,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
public void onInit() throws Exception {
|
||||
ZookeeperConfiguration configuration = new ZookeeperConfiguration(this.zookeeperConnect);
|
||||
configuration.setBufferSize(socketBufferSize);
|
||||
configuration.setMaxWait(defaultMaxWait);
|
||||
configuration.setMaxWait(maxWait);
|
||||
DefaultConnectionFactory defaultConnectionFactory =
|
||||
new DefaultConnectionFactory(configuration);
|
||||
defaultConnectionFactory.afterPropertiesSet();
|
||||
@@ -310,13 +214,13 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
|
||||
simpleRetryPolicy.setMaxAttempts(METADATA_VERIFICATION_RETRY_ATTEMPTS);
|
||||
simpleRetryPolicy.setMaxAttempts(10);
|
||||
retryTemplate.setRetryPolicy(simpleRetryPolicy);
|
||||
|
||||
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
|
||||
backOffPolicy.setInitialInterval(METADATA_VERIFICATION_RETRY_INITIAL_INTERVAL);
|
||||
backOffPolicy.setMultiplier(METADATA_VERIFICATION_RETRY_BACKOFF_MULTIPLIER);
|
||||
backOffPolicy.setMaxInterval(METADATA_VERIFICATION_MAX_INTERVAL);
|
||||
backOffPolicy.setInitialInterval(100);
|
||||
backOffPolicy.setMultiplier((double) 2);
|
||||
backOffPolicy.setMaxInterval(1000);
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
retryOperations = retryTemplate;
|
||||
}
|
||||
@@ -340,61 +244,28 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultReplicationFactor(int defaultReplicationFactor) {
|
||||
this.defaultReplicationFactor = defaultReplicationFactor;
|
||||
public void setReplicationFactor(int replicationFactor) {
|
||||
this.replicationFactor = replicationFactor;
|
||||
}
|
||||
|
||||
public void setDefaultCompressionCodec(String defaultCompressionCodec) {
|
||||
this.defaultCompressionCodec = defaultCompressionCodec;
|
||||
public void setRequiredAcks(int requiredAcks) {
|
||||
this.requiredAcks = requiredAcks;
|
||||
}
|
||||
|
||||
public void setDefaultRequiredAcks(int defaultRequiredAcks) {
|
||||
this.defaultRequiredAcks = defaultRequiredAcks;
|
||||
public void setQueueSize(int queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default auto commit enabled property; This is used to commit the offset either automatically or
|
||||
* manually.
|
||||
* @param defaultAutoCommitEnabled
|
||||
*/
|
||||
public void setDefaultAutoCommitEnabled(boolean defaultAutoCommitEnabled) {
|
||||
this.defaultAutoCommitEnabled = defaultAutoCommitEnabled;
|
||||
}
|
||||
|
||||
public void setDefaultQueueSize(int defaultQueueSize) {
|
||||
this.defaultQueueSize = defaultQueueSize;
|
||||
}
|
||||
|
||||
public void setDefaultFetchSize(int defaultFetchSize) {
|
||||
this.defaultFetchSize = defaultFetchSize;
|
||||
public void setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
}
|
||||
|
||||
public void setDefaultMinPartitionCount(int defaultMinPartitionCount) {
|
||||
this.defaultMinPartitionCount = defaultMinPartitionCount;
|
||||
}
|
||||
|
||||
public void setDefaultMaxWait(int defaultMaxWait) {
|
||||
this.defaultMaxWait = defaultMaxWait;
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public boolean isResetOffsets() {
|
||||
return resetOffsets;
|
||||
}
|
||||
|
||||
public void setResetOffsets(boolean resetOffsets) {
|
||||
this.resetOffsets = resetOffsets;
|
||||
}
|
||||
|
||||
public StartOffset getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public void setStartOffset(StartOffset startOffset) {
|
||||
this.startOffset = startOffset;
|
||||
public void setMaxWait(int maxWait) {
|
||||
this.maxWait = maxWait;
|
||||
}
|
||||
|
||||
public int getZkSessionTimeout() {
|
||||
@@ -418,7 +289,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, KafkaConsumerProperties 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
|
||||
@@ -429,57 +300,49 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
// 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 = this.startOffset != null ?
|
||||
startOffset.getReferencePoint() : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime());
|
||||
long referencePoint = properties.getStartOffset() != null ?
|
||||
properties.getStartOffset().getReferencePoint() : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime());
|
||||
return createKafkaConsumer(name, inputChannel, properties, consumerGroup, referencePoint);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> doBindProducer(String name, MessageChannel moduleOutputChannel, KafkaProducerProperties properties) {
|
||||
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
KafkaPropertiesAccessor producerPropertiesAccessor = new KafkaPropertiesAccessor(properties);
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Using kafka topic for outbound: " + name);
|
||||
}
|
||||
|
||||
validateTopicName(name);
|
||||
|
||||
int numPartitions = producerPropertiesAccessor.getNumberOfKafkaPartitionsForProducer();
|
||||
|
||||
Collection<Partition> partitions = ensureTopicCreated(name, numPartitions, defaultReplicationFactor);
|
||||
int numPartitions = Math.max(defaultMinPartitionCount, properties.getPartitionCount());
|
||||
Collection<Partition> partitions = ensureTopicCreated(name, numPartitions, replicationFactor);
|
||||
|
||||
topicsInUse.put(name, partitions);
|
||||
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>(
|
||||
name, byte[].class, byte[].class, BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setSync(isSyncProducer());
|
||||
producerMetadata.setCompressionType(ProducerMetadata.CompressionType.valueOf(
|
||||
producerPropertiesAccessor.getCompressionCodec(this.defaultCompressionCodec)));
|
||||
producerMetadata.setBatchBytes(producerPropertiesAccessor.getBatchSize(this.defaultBatchSize));
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>(name, byte[].class, byte[].class,
|
||||
BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setSync(properties.isSync());
|
||||
producerMetadata.setCompressionType(properties.getCompressionType());
|
||||
producerMetadata.setBatchBytes(properties.getBufferSize());
|
||||
Properties additionalProps = new Properties();
|
||||
additionalProps.put(ProducerConfig.ACKS_CONFIG,
|
||||
String.valueOf(producerPropertiesAccessor.getRequiredAcks(this
|
||||
.defaultRequiredAcks)));
|
||||
additionalProps.put(ProducerConfig.LINGER_MS_CONFIG,
|
||||
String.valueOf(producerPropertiesAccessor.getBatchTimeout(this
|
||||
.defaultBatchTimeout)));
|
||||
ProducerFactoryBean<byte[], byte[]> producerFB =
|
||||
new ProducerFactoryBean<>(producerMetadata, brokers, additionalProps);
|
||||
additionalProps.put(ProducerConfig.ACKS_CONFIG, String.valueOf(requiredAcks));
|
||||
additionalProps.put(ProducerConfig.LINGER_MS_CONFIG, String.valueOf(properties.getBatchTimeout()));
|
||||
ProducerFactoryBean<byte[], byte[]> producerFB = new ProducerFactoryBean<>(producerMetadata, brokers, additionalProps);
|
||||
|
||||
try {
|
||||
final ProducerConfiguration<byte[], byte[]> producerConfiguration = new ProducerConfiguration<>(
|
||||
producerMetadata, producerFB.getObject());
|
||||
producerConfiguration.setProducerListener(producerListener);
|
||||
|
||||
MessageHandler handler = new SendingHandler(name, producerPropertiesAccessor,
|
||||
partitions.size(), producerConfiguration);
|
||||
MessageHandler handler = new SendingHandler(name, properties, partitions.size(), producerConfiguration);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel,
|
||||
handler);
|
||||
consumer.setBeanFactory(this.getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, producerPropertiesAccessor);
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
|
||||
consumer.start();
|
||||
return producerBinding;
|
||||
}
|
||||
@@ -542,62 +405,44 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> createKafkaConsumer(String name, final MessageChannel moduleInputChannel, Properties properties,
|
||||
String group, long referencePoint) {
|
||||
|
||||
validateConsumerProperties(groupedName(name, group), properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
KafkaPropertiesAccessor accessor = new KafkaPropertiesAccessor(properties);
|
||||
|
||||
int maxConcurrency = accessor.getConcurrency(defaultConcurrency);
|
||||
private Binding<MessageChannel> createKafkaConsumer(String name, final MessageChannel moduleInputChannel,
|
||||
KafkaConsumerProperties properties, String group, long referencePoint) {
|
||||
|
||||
validateTopicName(name);
|
||||
|
||||
int numPartitions = accessor.getNumberOfKafkaPartitionsForConsumer();
|
||||
Collection<Partition> allPartitions = ensureTopicCreated(name, numPartitions, defaultReplicationFactor);
|
||||
int minKafkaPartitions = properties.getMinPartitionCount();
|
||||
int instance = properties.getInstanceCount();
|
||||
if (instance == 0) {
|
||||
throw new IllegalArgumentException("Instance count cannot be zero");
|
||||
}
|
||||
int numPartitions = Math.max(minKafkaPartitions, instance * properties.getConcurrency());
|
||||
Collection<Partition> allPartitions = ensureTopicCreated(name, numPartitions, replicationFactor);
|
||||
|
||||
Decoder<byte[]> valueDecoder = new DefaultDecoder(null);
|
||||
Decoder<byte[]> keyDecoder = new DefaultDecoder(null);
|
||||
|
||||
Collection<Partition> listenedPartitions;
|
||||
|
||||
int moduleCount = accessor.getCount();
|
||||
|
||||
if (moduleCount == 1) {
|
||||
if (instance == 1) {
|
||||
listenedPartitions = allPartitions;
|
||||
}
|
||||
else {
|
||||
listenedPartitions = new ArrayList<Partition>();
|
||||
listenedPartitions = new ArrayList<>();
|
||||
for (Partition partition : allPartitions) {
|
||||
// divide partitions across modules
|
||||
if (accessor.getPartitionIndex() != -1) {
|
||||
if ((partition.getId() % moduleCount) == accessor.getPartitionIndex()) {
|
||||
listenedPartitions.add(partition);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int moduleSequence = accessor.getSequence();
|
||||
if (moduleCount == 0) {
|
||||
throw new IllegalArgumentException("The Kafka transport does not support 0-count modules");
|
||||
}
|
||||
else {
|
||||
// sequence numbers are zero-based
|
||||
if ((partition.getId() % moduleCount) == (moduleSequence - 1)) {
|
||||
listenedPartitions.add(partition);
|
||||
}
|
||||
}
|
||||
if ((partition.getId() % instance) == properties.getInstanceIndex()) {
|
||||
listenedPartitions.add(partition);
|
||||
}
|
||||
}
|
||||
}
|
||||
topicsInUse.put(name, listenedPartitions);
|
||||
ReceivingHandler rh = new ReceivingHandler();
|
||||
ReceivingHandler rh = new ReceivingHandler(properties);
|
||||
rh.setOutputChannel(moduleInputChannel);
|
||||
|
||||
final FixedSubscriberChannel bridge = new FixedSubscriberChannel(rh);
|
||||
bridge.setBeanName("bridge." + name);
|
||||
|
||||
final KafkaMessageListenerContainer messageListenerContainer =
|
||||
createMessageListenerContainer(accessor, group, maxConcurrency, listenedPartitions,
|
||||
referencePoint);
|
||||
createMessageListenerContainer(properties, group, null, listenedPartitions, referencePoint);
|
||||
|
||||
final KafkaMessageDrivenChannelAdapter kafkaMessageDrivenChannelAdapter =
|
||||
new KafkaMessageDrivenChannelAdapter(messageListenerContainer);
|
||||
@@ -605,8 +450,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
kafkaMessageDrivenChannelAdapter.setKeyDecoder(keyDecoder);
|
||||
kafkaMessageDrivenChannelAdapter.setPayloadDecoder(valueDecoder);
|
||||
kafkaMessageDrivenChannelAdapter.setOutputChannel(bridge);
|
||||
kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(accessor.getDefaultAutoCommitEnabled(this
|
||||
.defaultAutoCommitEnabled));
|
||||
kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(properties.isAutoCommitOffset());
|
||||
kafkaMessageDrivenChannelAdapter.afterPropertiesSet();
|
||||
kafkaMessageDrivenChannelAdapter.start();
|
||||
|
||||
@@ -632,25 +476,14 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
String groupedName = groupedName(name, group);
|
||||
edc.setBeanName("inbound." + groupedName);
|
||||
|
||||
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<>(name, group, moduleInputChannel, edc, accessor);
|
||||
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<>(name, group, moduleInputChannel, edc);
|
||||
edc.start();
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
public KafkaMessageListenerContainer createMessageListenerContainer(Properties properties, String group,
|
||||
int maxConcurrency, String topic, long referencePoint) {
|
||||
return createMessageListenerContainer(new KafkaPropertiesAccessor(properties), group, maxConcurrency, topic,
|
||||
null, referencePoint);
|
||||
}
|
||||
|
||||
private KafkaMessageListenerContainer createMessageListenerContainer(KafkaPropertiesAccessor accessor,
|
||||
String group, int maxConcurrency, Collection<Partition> listenedPartitions, long referencePoint) {
|
||||
return createMessageListenerContainer(accessor, group, maxConcurrency, null, listenedPartitions, referencePoint);
|
||||
}
|
||||
|
||||
private KafkaMessageListenerContainer createMessageListenerContainer(KafkaPropertiesAccessor accessor,
|
||||
String group, int maxConcurrency, String topic, Collection<Partition> listenedPartitions,
|
||||
long referencePoint) {
|
||||
KafkaMessageListenerContainer createMessageListenerContainer(KafkaConsumerProperties consumerProperties,
|
||||
String group, String topic, Collection<Partition> listenedPartitions,
|
||||
long referencePoint) {
|
||||
Assert.isTrue(StringUtils.hasText(topic) ^ !CollectionUtils.isEmpty(listenedPartitions),
|
||||
"Exactly one of topic or a list of listened partitions must be provided");
|
||||
KafkaMessageListenerContainer messageListenerContainer;
|
||||
@@ -664,15 +497,15 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Listening to topic " + topic);
|
||||
}
|
||||
// if we have less target partitions than target concurrency, adjust accordingly
|
||||
messageListenerContainer.setConcurrency(Math.min(maxConcurrency, listenedPartitions.size()));
|
||||
// if we have fewer target partitions than target concurrency, adjust accordingly
|
||||
messageListenerContainer.setConcurrency(Math.min(consumerProperties.getConcurrency(), listenedPartitions.size()));
|
||||
OffsetManager offsetManager = createOffsetManager(group, referencePoint);
|
||||
if (resetOffsets) {
|
||||
if (consumerProperties.isResetOffsets()) {
|
||||
offsetManager.resetOffsets(listenedPartitions);
|
||||
}
|
||||
messageListenerContainer.setOffsetManager(offsetManager);
|
||||
messageListenerContainer.setQueueSize(accessor.getProperty(QUEUE_SIZE, defaultQueueSize));
|
||||
messageListenerContainer.setMaxFetch(accessor.getProperty(FETCH_SIZE, defaultFetchSize));
|
||||
messageListenerContainer.setQueueSize(queueSize);
|
||||
messageListenerContainer.setMaxFetch(fetchSize);
|
||||
return messageListenerContainer;
|
||||
}
|
||||
|
||||
@@ -711,60 +544,18 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
}
|
||||
|
||||
private class KafkaPropertiesAccessor extends DefaultBindingPropertiesAccessor {
|
||||
|
||||
public KafkaPropertiesAccessor(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public int getNumberOfKafkaPartitionsForProducer() {
|
||||
int nextModuleCount = getNextModuleCount();
|
||||
if (nextModuleCount == 0) {
|
||||
throw new IllegalArgumentException("Module count cannot be zero");
|
||||
}
|
||||
int nextModuleConcurrency = getProperty(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, defaultConcurrency);
|
||||
int minKafkaPartitions = getMinPartitionCount(defaultMinPartitionCount);
|
||||
return Math.max(minKafkaPartitions, nextModuleCount * nextModuleConcurrency);
|
||||
}
|
||||
|
||||
public int getNumberOfKafkaPartitionsForConsumer() {
|
||||
int concurrency = getConcurrency(defaultConcurrency);
|
||||
int minKafkaPartitions = getMinPartitionCount(defaultMinPartitionCount);
|
||||
int moduleCount = getCount();
|
||||
if (moduleCount == 0) {
|
||||
throw new IllegalArgumentException("Module count cannot be zero");
|
||||
}
|
||||
return Math.max(minKafkaPartitions, moduleCount * concurrency);
|
||||
}
|
||||
|
||||
public String getCompressionCodec(String defaultValue) {
|
||||
return getProperty(COMPRESSION_CODEC, defaultValue);
|
||||
}
|
||||
|
||||
public int getRequiredAcks(int defaultRequiredAcks) {
|
||||
return getProperty(REQUIRED_ACKS, defaultRequiredAcks);
|
||||
}
|
||||
|
||||
public boolean getDefaultAutoCommitEnabled(boolean defaultAutoCommitEnabled) {
|
||||
return getProperty(AUTO_COMMIT_ENABLED, defaultAutoCommitEnabled);
|
||||
}
|
||||
|
||||
public int getMinPartitionCount(int defaultPartitionCount) {
|
||||
return getProperty(BinderPropertyKeys.MIN_PARTITION_COUNT, defaultPartitionCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ReceivingHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
public ReceivingHandler() {
|
||||
this.setBeanFactory(KafkaMessageChannelBinder.this.getBeanFactory());
|
||||
private KafkaConsumerProperties consumerProperties;
|
||||
|
||||
public ReceivingHandler(KafkaConsumerProperties consumerProperties) {
|
||||
this.consumerProperties = consumerProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
if (Mode.embeddedHeaders.equals(mode)) {
|
||||
if (Mode.embeddedHeaders.equals(consumerProperties.getMode())) {
|
||||
MessageValues messageValues;
|
||||
try {
|
||||
messageValues = embeddedHeadersMessageConverter.extractHeaders((Message<byte[]>) requestMessage,
|
||||
@@ -805,40 +596,43 @@ public class KafkaMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private final String topicName;
|
||||
|
||||
private final KafkaProducerProperties producerProperties;
|
||||
|
||||
private final int numberOfKafkaPartitions;
|
||||
|
||||
private final ProducerConfiguration<byte[], byte[]> producerConfiguration;
|
||||
|
||||
private final PartitionHandler partitionHandler;
|
||||
|
||||
private SendingHandler(String topicName, KafkaPropertiesAccessor properties, int numberOfPartitions,
|
||||
ProducerConfiguration<byte[], byte[]> producerConfiguration) {
|
||||
private SendingHandler(String topicName, KafkaProducerProperties properties, int numberOfPartitions,
|
||||
ProducerConfiguration<byte[], byte[]> producerConfiguration) {
|
||||
this.topicName = topicName;
|
||||
producerProperties = properties;
|
||||
this.numberOfKafkaPartitions = numberOfPartitions;
|
||||
ConfigurableListableBeanFactory beanFactory = KafkaMessageChannelBinder.this.getBeanFactory();
|
||||
this.setBeanFactory(beanFactory);
|
||||
this.producerConfiguration = producerConfiguration;
|
||||
this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector,
|
||||
properties, numberOfPartitions);
|
||||
properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
int targetPartition;
|
||||
if (this.partitionHandler.isPartitionedModule()) {
|
||||
if (producerProperties.isPartitioned()) {
|
||||
targetPartition = this.partitionHandler.determinePartition(message);
|
||||
}
|
||||
else {
|
||||
targetPartition = roundRobin() % numberOfKafkaPartitions;
|
||||
}
|
||||
|
||||
if (Mode.embeddedHeaders.equals(mode)) {
|
||||
if (Mode.embeddedHeaders.equals(producerProperties.getMode())) {
|
||||
MessageValues transformed = serializePayloadIfNecessary(message);
|
||||
byte[] messageToSend = embeddedHeadersMessageConverter.embedHeaders(transformed,
|
||||
KafkaMessageChannelBinder.this.headersToMap);
|
||||
producerConfiguration.send(topicName, targetPartition, null, messageToSend);
|
||||
}
|
||||
else if (Mode.raw.equals(mode)) {
|
||||
else if (Mode.raw.equals(producerProperties.getMode())) {
|
||||
Object contentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
|
||||
if (contentType != null
|
||||
&& !contentType.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.integration.kafka.support.ProducerMetadata;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class KafkaProducerProperties extends ProducerProperties {
|
||||
|
||||
private int bufferSize = 16384;
|
||||
|
||||
private ProducerMetadata.CompressionType compressionType = ProducerMetadata.CompressionType.none;
|
||||
|
||||
private boolean sync = false;
|
||||
|
||||
private KafkaMessageChannelBinder.Mode mode = KafkaMessageChannelBinder.Mode.embeddedHeaders;
|
||||
|
||||
private int batchTimeout = 0;
|
||||
|
||||
public int getBufferSize() {
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public ProducerMetadata.CompressionType getCompressionType() {
|
||||
return compressionType;
|
||||
}
|
||||
|
||||
public void setCompressionType(ProducerMetadata.CompressionType compressionType) {
|
||||
this.compressionType = compressionType;
|
||||
}
|
||||
|
||||
public boolean isSync() {
|
||||
return sync;
|
||||
}
|
||||
|
||||
public void setSync(boolean sync) {
|
||||
this.sync = sync;
|
||||
}
|
||||
|
||||
public KafkaMessageChannelBinder.Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(KafkaMessageChannelBinder.Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(int batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
}
|
||||
@@ -44,16 +44,13 @@ import org.springframework.util.ObjectUtils;
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(Binder.class)
|
||||
@Import({KryoCodecAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
|
||||
@EnableConfigurationProperties({KafkaBinderConfigurationProperties.class, KafkaBinderDefaultProperties.class})
|
||||
@EnableConfigurationProperties({KafkaBinderConfigurationProperties.class})
|
||||
@PropertySource("classpath:/META-INF/spring-cloud-stream/kafka-binder.properties")
|
||||
public class KafkaServiceAutoConfiguration {
|
||||
public class KafkaBinderConfiguration {
|
||||
|
||||
@Autowired
|
||||
private Codec codec;
|
||||
|
||||
@Autowired
|
||||
private KafkaBinderDefaultProperties kafkaBinderDefaultProperties;
|
||||
|
||||
@Autowired
|
||||
private KafkaBinderConfigurationProperties kafkaBinderConfigurationProperties;
|
||||
|
||||
@@ -77,29 +74,19 @@ public class KafkaServiceAutoConfiguration {
|
||||
: new KafkaMessageChannelBinder(zookeeperConnect(), kafkaConnectionString, zkConnectionString,
|
||||
headers);
|
||||
kafkaMessageChannelBinder.setCodec(codec);
|
||||
kafkaMessageChannelBinder.setMode(kafkaBinderConfigurationProperties.getMode());
|
||||
kafkaMessageChannelBinder.setOffsetUpdateTimeWindow(kafkaBinderConfigurationProperties.getOffsetUpdateTimeWindow());
|
||||
kafkaMessageChannelBinder.setOffsetUpdateCount(kafkaBinderConfigurationProperties.getOffsetUpdateCount());
|
||||
kafkaMessageChannelBinder.setOffsetUpdateShutdownTimeout(kafkaBinderConfigurationProperties.getOffsetUpdateShutdownTimeout());
|
||||
|
||||
kafkaMessageChannelBinder.setResetOffsets(kafkaBinderConfigurationProperties.isResetOffsets());
|
||||
kafkaMessageChannelBinder.setStartOffset(kafkaBinderConfigurationProperties.getStartOffset());
|
||||
|
||||
kafkaMessageChannelBinder.setZkSessionTimeout(kafkaBinderConfigurationProperties.getZkSessionTimeout());
|
||||
kafkaMessageChannelBinder.setZkConnectionTimeout(kafkaBinderConfigurationProperties.getZkConnectionTimeout());
|
||||
|
||||
kafkaMessageChannelBinder.setSyncProducer(kafkaBinderConfigurationProperties.isSyncProducer());
|
||||
|
||||
kafkaMessageChannelBinder.setDefaultAutoCommitEnabled(kafkaBinderDefaultProperties.isAutoCommitEnabled());
|
||||
kafkaMessageChannelBinder.setDefaultBatchSize(kafkaBinderDefaultProperties.getBatchSize());
|
||||
kafkaMessageChannelBinder.setDefaultBatchTimeout(kafkaBinderDefaultProperties.getBatchTimeout());
|
||||
kafkaMessageChannelBinder.setDefaultCompressionCodec(kafkaBinderDefaultProperties.getCompressionCodec());
|
||||
kafkaMessageChannelBinder.setDefaultConcurrency(kafkaBinderDefaultProperties.getConcurrency());
|
||||
kafkaMessageChannelBinder.setDefaultFetchSize(kafkaBinderDefaultProperties.getFetchSize());
|
||||
kafkaMessageChannelBinder.setDefaultMinPartitionCount(kafkaBinderDefaultProperties.getMinPartitionCount());
|
||||
kafkaMessageChannelBinder.setDefaultQueueSize(kafkaBinderDefaultProperties.getQueueSize());
|
||||
kafkaMessageChannelBinder.setDefaultReplicationFactor(kafkaBinderDefaultProperties.getReplicationFactor());
|
||||
kafkaMessageChannelBinder.setDefaultRequiredAcks(kafkaBinderDefaultProperties.getRequiredAcks());
|
||||
kafkaMessageChannelBinder.setFetchSize(kafkaBinderConfigurationProperties.getFetchSize());
|
||||
kafkaMessageChannelBinder.setDefaultMinPartitionCount(kafkaBinderConfigurationProperties.getMinPartitionCount());
|
||||
kafkaMessageChannelBinder.setQueueSize(kafkaBinderConfigurationProperties.getQueueSize());
|
||||
kafkaMessageChannelBinder.setReplicationFactor(kafkaBinderConfigurationProperties.getReplicationFactor());
|
||||
kafkaMessageChannelBinder.setRequiredAcks(kafkaBinderConfigurationProperties.getRequiredAcks());
|
||||
kafkaMessageChannelBinder.setMaxWait(kafkaBinderConfigurationProperties.getMaxWait());
|
||||
|
||||
kafkaMessageChannelBinder.setProducerListener(producerListener);
|
||||
return kafkaMessageChannelBinder;
|
||||
@@ -47,10 +47,7 @@ class KafkaBinderConfigurationProperties {
|
||||
|
||||
private int offsetUpdateShutdownTimeout;
|
||||
|
||||
private boolean resetOffsets = false;
|
||||
|
||||
private KafkaMessageChannelBinder.StartOffset startOffset;
|
||||
|
||||
private int maxWait = 100;
|
||||
/**
|
||||
* ZK session timeout in milliseconds.
|
||||
*/
|
||||
@@ -61,10 +58,15 @@ class KafkaBinderConfigurationProperties {
|
||||
*/
|
||||
private int zkConnectionTimeout;
|
||||
|
||||
/**
|
||||
* Flag to indicate if the Kafka Producer is synchronous or asynchronous.
|
||||
*/
|
||||
private boolean syncProducer = false;
|
||||
private int requiredAcks = 1;
|
||||
|
||||
private int replicationFactor = 1;
|
||||
|
||||
private int fetchSize = 1024 * 1024;
|
||||
|
||||
private int minPartitionCount = 1;
|
||||
|
||||
private int queueSize;
|
||||
|
||||
public String getZkConnectionString() {
|
||||
return toConnectionString(this.zkNodes, this.defaultZkPort);
|
||||
@@ -131,23 +133,6 @@ class KafkaBinderConfigurationProperties {
|
||||
this.offsetUpdateShutdownTimeout = offsetUpdateShutdownTimeout;
|
||||
}
|
||||
|
||||
public KafkaMessageChannelBinder.StartOffset getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public void setStartOffset(KafkaMessageChannelBinder.StartOffset startOffset) {
|
||||
this.startOffset = startOffset;
|
||||
}
|
||||
|
||||
public boolean isResetOffsets() {
|
||||
return resetOffsets;
|
||||
}
|
||||
|
||||
public void setResetOffsets(boolean resetOffsets) {
|
||||
this.resetOffsets = resetOffsets;
|
||||
}
|
||||
|
||||
|
||||
public int getZkSessionTimeout() {
|
||||
return this.zkSessionTimeout;
|
||||
}
|
||||
@@ -164,14 +149,6 @@ class KafkaBinderConfigurationProperties {
|
||||
this.zkConnectionTimeout = zkConnectionTimeout;
|
||||
}
|
||||
|
||||
public boolean isSyncProducer() {
|
||||
return this.syncProducer;
|
||||
}
|
||||
|
||||
public void setSyncProducer(boolean syncProducer) {
|
||||
this.syncProducer = syncProducer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of host values to a comma-separated String.
|
||||
*
|
||||
@@ -189,4 +166,53 @@ class KafkaBinderConfigurationProperties {
|
||||
}
|
||||
return StringUtils.arrayToCommaDelimitedString(fullyFormattedHosts);
|
||||
}
|
||||
|
||||
public int getMaxWait() {
|
||||
return maxWait;
|
||||
}
|
||||
|
||||
public void setMaxWait(int maxWait) {
|
||||
this.maxWait = maxWait;
|
||||
}
|
||||
|
||||
public int getRequiredAcks() {
|
||||
return requiredAcks;
|
||||
}
|
||||
|
||||
public void setRequiredAcks(int requiredAcks) {
|
||||
this.requiredAcks = requiredAcks;
|
||||
}
|
||||
|
||||
public int getReplicationFactor() {
|
||||
return replicationFactor;
|
||||
}
|
||||
|
||||
public void setReplicationFactor(int replicationFactor) {
|
||||
this.replicationFactor = replicationFactor;
|
||||
}
|
||||
|
||||
public int getFetchSize() {
|
||||
return fetchSize;
|
||||
}
|
||||
|
||||
public void setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
}
|
||||
|
||||
public int getMinPartitionCount() {
|
||||
return minPartitionCount;
|
||||
}
|
||||
|
||||
public void setMinPartitionCount(int minPartitionCount) {
|
||||
this.minPartitionCount = minPartitionCount;
|
||||
}
|
||||
|
||||
public int getQueueSize() {
|
||||
return queueSize;
|
||||
}
|
||||
|
||||
public void setQueueSize(int queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,127 +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.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@ConfigurationProperties(value = "spring.cloud.stream.binder.kafka.default")
|
||||
public class KafkaBinderDefaultProperties {
|
||||
|
||||
private int batchSize;
|
||||
|
||||
private long batchTimeout;
|
||||
|
||||
private int requiredAcks;
|
||||
|
||||
private int replicationFactor;
|
||||
|
||||
private int concurrency;
|
||||
|
||||
private String compressionCodec;
|
||||
|
||||
private boolean autoCommitEnabled;
|
||||
|
||||
private int fetchSize;
|
||||
|
||||
private int minPartitionCount;
|
||||
|
||||
private int queueSize;
|
||||
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public long getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(long batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
|
||||
public int getRequiredAcks() {
|
||||
return requiredAcks;
|
||||
}
|
||||
|
||||
public void setRequiredAcks(int requiredAcks) {
|
||||
this.requiredAcks = requiredAcks;
|
||||
}
|
||||
|
||||
public int getReplicationFactor() {
|
||||
return replicationFactor;
|
||||
}
|
||||
|
||||
public void setReplicationFactor(int replicationFactor) {
|
||||
this.replicationFactor = replicationFactor;
|
||||
}
|
||||
|
||||
public int getConcurrency() {
|
||||
return concurrency;
|
||||
}
|
||||
|
||||
public void setConcurrency(int concurrency) {
|
||||
this.concurrency = concurrency;
|
||||
}
|
||||
|
||||
public String getCompressionCodec() {
|
||||
return compressionCodec;
|
||||
}
|
||||
|
||||
public void setCompressionCodec(String compressionCodec) {
|
||||
this.compressionCodec = compressionCodec;
|
||||
}
|
||||
|
||||
public boolean isAutoCommitEnabled() {
|
||||
return autoCommitEnabled;
|
||||
}
|
||||
|
||||
public void setAutoCommitEnabled(boolean autoCommitEnabled) {
|
||||
this.autoCommitEnabled = autoCommitEnabled;
|
||||
}
|
||||
|
||||
public int getFetchSize() {
|
||||
return fetchSize;
|
||||
}
|
||||
|
||||
public void setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
}
|
||||
|
||||
public int getMinPartitionCount() {
|
||||
return minPartitionCount;
|
||||
}
|
||||
|
||||
public void setMinPartitionCount(int minPartitionCount) {
|
||||
this.minPartitionCount = minPartitionCount;
|
||||
}
|
||||
|
||||
public int getQueueSize() {
|
||||
return queueSize;
|
||||
}
|
||||
|
||||
public void setQueueSize(int queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,11 @@ spring.cloud.stream.binder.kafka.offsetUpdateCount=0
|
||||
spring.cloud.stream.binder.kafka.offsetUpdateShutdownTimeout=2000
|
||||
spring.cloud.stream.binder.kafka.zkSessionTimeout=10000
|
||||
spring.cloud.stream.binder.kafka.zkConnectionTimeout=10000
|
||||
spring.cloud.stream.binder.kafka.syncProducer=false
|
||||
spring.cloud.stream.binder.kafka.default.batchSize=16384
|
||||
spring.cloud.stream.binder.kafka.default.batchTimeout=0
|
||||
spring.cloud.stream.binder.kafka.default.requiredAcks=1
|
||||
spring.cloud.stream.binder.kafka.default.replicationFactor=1
|
||||
spring.cloud.stream.binder.kafka.default.concurrency=1
|
||||
spring.cloud.stream.binder.kafka.default.compressionCodec=none
|
||||
spring.cloud.stream.binder.kafka.default.autoCommitEnabled=true
|
||||
spring.cloud.stream.binder.kafka.default.fetchSize=1048576
|
||||
spring.cloud.stream.binder.kafka.default.minPartitionCount=1
|
||||
spring.cloud.stream.binder.kafka.default.queueSize=8192
|
||||
spring.cloud.stream.binder.kafka.batchSize=16384
|
||||
spring.cloud.stream.binder.kafka.batchTimeout=0
|
||||
spring.cloud.stream.binder.kafka.requiredAcks=1
|
||||
spring.cloud.stream.binder.kafka.replicationFactor=1
|
||||
spring.cloud.stream.binder.kafka.compressionCodec=none
|
||||
spring.cloud.stream.binder.kafka.fetchSize=1048576
|
||||
spring.cloud.stream.binder.kafka.minPartitionCount=1
|
||||
spring.cloud.stream.binder.kafka.queueSize=8192
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
kafka:\
|
||||
org.springframework.cloud.stream.binder.kafka.config.KafkaServiceAutoConfiguration
|
||||
org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
|
||||
|
||||
@@ -27,19 +27,17 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kafka.api.OffsetRequest;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
@@ -52,33 +50,27 @@ import org.springframework.integration.kafka.core.Partition;
|
||||
import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer;
|
||||
import org.springframework.integration.kafka.listener.MessageListener;
|
||||
import org.springframework.integration.kafka.support.ProducerConfiguration;
|
||||
import org.springframework.integration.kafka.support.ProducerMetadata;
|
||||
import org.springframework.integration.kafka.support.ZookeeperConnect;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import kafka.api.OffsetRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link KafkaMessageChannelBinder}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
public class KafkaBinderTests extends PartitionCapableBinderTests<KafkaTestBinder, KafkaConsumerProperties, KafkaProducerProperties> {
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = KafkaMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
static {
|
||||
System.setProperty("SCS_KAFKA_TEST_EMBEDDED", "true");
|
||||
}
|
||||
|
||||
@ClassRule
|
||||
public static KafkaTestSupport kafkaTestSupport = new KafkaTestSupport();
|
||||
public static KafkaTestSupport kafkaTestSupport = new KafkaTestSupport(true);
|
||||
|
||||
private KafkaTestBinder binder;
|
||||
|
||||
@@ -88,13 +80,23 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
protected KafkaTestBinder getBinder() {
|
||||
if (binder == null) {
|
||||
binder = createKafkaTestBinder();
|
||||
binder = new KafkaTestBinder(kafkaTestSupport);
|
||||
}
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KafkaConsumerProperties createConsumerProperties() {
|
||||
return new KafkaConsumerProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KafkaProducerProperties createProducerProperties() {
|
||||
return new KafkaProducerProperties();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
String multiplier = System.getenv("KAFKA_TIMEOUT_MULTIPLIER");
|
||||
@@ -103,10 +105,6 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
}
|
||||
|
||||
protected KafkaTestBinder createKafkaTestBinder() {
|
||||
return new KafkaTestBinder(kafkaTestSupport, KafkaMessageChannelBinder.Mode.embeddedHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean usesExplicitRouting() {
|
||||
return false;
|
||||
@@ -121,11 +119,11 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
public Spy spyOn(final String name) {
|
||||
KafkaMessageChannelBinder.validateTopicName(name);
|
||||
|
||||
KafkaTestBinder binderWrapper = (KafkaTestBinder) getBinder();
|
||||
KafkaTestBinder binderWrapper = getBinder();
|
||||
// Rewind offset, as tests will have typically already sent the messages we're trying to consume
|
||||
|
||||
KafkaMessageListenerContainer messageListenerContainer = binderWrapper.getCoreBinder().createMessageListenerContainer(
|
||||
new Properties(), UUID.randomUUID().toString(), 1, name, OffsetRequest.EarliestTime());
|
||||
createConsumerProperties(), UUID.randomUUID().toString(), name, null, OffsetRequest.EarliestTime());
|
||||
|
||||
final BlockingQueue<KafkaMessage> messages = new ArrayBlockingQueue<KafkaMessage>(10);
|
||||
|
||||
@@ -155,21 +153,22 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testCompression() throws Exception {
|
||||
final String[] codecs = new String[] { null, "none", "gzip", "snappy" };
|
||||
final ProducerMetadata.CompressionType[] codecs = new ProducerMetadata.CompressionType[] {
|
||||
ProducerMetadata.CompressionType.none,
|
||||
ProducerMetadata.CompressionType.gzip,
|
||||
ProducerMetadata.CompressionType.snappy };
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
|
||||
for (String codec : codecs) {
|
||||
for (ProducerMetadata.CompressionType codec : codecs) {
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties props = new Properties();
|
||||
if (codec != null) {
|
||||
props.put(KafkaMessageChannelBinder.COMPRESSION_CODEC, codec);
|
||||
}
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, props);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
KafkaProducerProperties producerProperties = new KafkaProducerProperties();
|
||||
producerProperties.setCompressionType(codec);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, new KafkaConsumerProperties());
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -187,14 +186,14 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
KafkaTestBinder binder = (KafkaTestBinder) getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties producerProperties = new Properties();
|
||||
producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "10");
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "10");
|
||||
KafkaProducerProperties producerProperties = new KafkaProducerProperties();
|
||||
producerProperties.setPartitionCount(10);
|
||||
KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties();
|
||||
consumerProperties.setMinPartitionCount(10);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
@@ -212,86 +211,20 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountDoesNotOverrideModuleCountAndConcurrencyIfSmaller() throws Exception {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
KafkaTestBinder binder = (KafkaTestBinder) getBinder();
|
||||
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties producerProps = new Properties();
|
||||
producerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
producerProps.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "6");
|
||||
Properties consumerProps = new Properties();
|
||||
consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
consumerProps.put(BinderPropertyKeys.CONCURRENCY, "6");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProps);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertNotNull(inbound);
|
||||
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(6));
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountOverridesModuleCountAndConcurrencyIfLarger() throws Exception {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
KafkaTestBinder binder = (KafkaTestBinder) getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties producerProps = new Properties();
|
||||
producerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "6");
|
||||
producerProps.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "5");
|
||||
Properties consumerProps = new Properties();
|
||||
consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "6");
|
||||
consumerProps.put(BinderPropertyKeys.CONCURRENCY, "5");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProps);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertNotNull(inbound);
|
||||
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(6));
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountDoesNotOverridePartitioningIfSmaller() throws Exception {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
KafkaTestBinder binder = (KafkaTestBinder) getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties producerProperties = new Properties();
|
||||
producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "3");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "5");
|
||||
producerProperties.put(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, "payload");
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "3");
|
||||
KafkaProducerProperties producerProperties = new KafkaProducerProperties();
|
||||
producerProperties.setPartitionCount(5);
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload"));
|
||||
KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties();
|
||||
consumerProperties.setMinPartitionCount(3);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
@@ -314,16 +247,15 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
KafkaTestBinder binder = (KafkaTestBinder) getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Properties producerProperties = new Properties();
|
||||
producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
producerProperties.put(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, "payload");
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
KafkaProducerProperties producerProperties = new KafkaProducerProperties();
|
||||
producerProperties.setPartitionCount(5);
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload"));
|
||||
KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties();
|
||||
consumerProperties.setMinPartitionCount(5);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
@@ -351,14 +283,13 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
binder.bindProducer(testTopicName,output,properties);
|
||||
binder.bindProducer(testTopicName, output, new KafkaProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, new KafkaConsumerProperties());
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1, not(nullValue()));
|
||||
assertThat(new String(receivedMessage1.getPayload()), equalTo(testPayload1));
|
||||
@@ -372,21 +303,16 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testEarliest() throws Exception {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()),
|
||||
kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString());
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
binder.bindProducer(testTopicName,output,properties);
|
||||
binder.bindProducer(testTopicName, output, new KafkaProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
KafkaConsumerProperties properties = new KafkaConsumerProperties();
|
||||
properties.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1, not(nullValue()));
|
||||
@@ -400,23 +326,18 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReset() throws Exception {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()),
|
||||
kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString());
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
binder.setResetOffsets(true);
|
||||
binder.afterPropertiesSet();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, properties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, new KafkaProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
KafkaConsumerProperties properties = new KafkaConsumerProperties();
|
||||
properties.setResetOffsets(true);
|
||||
properties.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
Binding<MessageChannel> consumerBinding =
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
@@ -431,8 +352,11 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
String testPayload3 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload3.getBytes()));
|
||||
|
||||
KafkaConsumerProperties properties2 = new KafkaConsumerProperties();
|
||||
properties2.setResetOffsets(true);
|
||||
properties2.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
consumerBinding =
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties2);
|
||||
Message<byte[]> receivedMessage4 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage4, not(nullValue()));
|
||||
assertThat(new String(receivedMessage4.getPayload()), equalTo(testPayload1));
|
||||
@@ -455,16 +379,15 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, properties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, new KafkaProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
KafkaConsumerProperties firstConsumerProperties = new KafkaConsumerProperties();
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, firstConsumerProperties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1, not(nullValue()));
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
@@ -478,7 +401,7 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
output.send(new GenericMessage<>(testPayload3.getBytes()));
|
||||
|
||||
consumerBinding =
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, new KafkaConsumerProperties());
|
||||
Message<byte[]> receivedMessage3 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage3, not(nullValue()));
|
||||
assertThat(new String(receivedMessage3.getPayload()), equalTo(testPayload3));
|
||||
@@ -490,7 +413,6 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
public void testSyncProducerMetadata() throws Exception {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()),
|
||||
kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString());
|
||||
binder.setSyncProducer(true);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
@@ -499,7 +421,9 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
DirectChannel output = new DirectChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, null);
|
||||
KafkaProducerProperties properties = new KafkaProducerProperties();
|
||||
properties.setSync(true);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, properties);
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(extractEndpoint(producerBinding));
|
||||
MessageHandler handler = (MessageHandler) accessor.getPropertyValue("handler");
|
||||
DirectFieldAccessor accessor1 = new DirectFieldAccessor(handler);
|
||||
|
||||
@@ -43,15 +43,9 @@ import com.esotericsoftware.kryo.Registration;
|
||||
* @author Gary Russell
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class KafkaTestBinder extends AbstractTestBinder<KafkaMessageChannelBinder> {
|
||||
public class KafkaTestBinder extends AbstractTestBinder<KafkaMessageChannelBinder, KafkaConsumerProperties, KafkaProducerProperties> {
|
||||
|
||||
public KafkaTestBinder(KafkaTestSupport kafkaTestSupport) {
|
||||
this(kafkaTestSupport, KafkaMessageChannelBinder.Mode.embeddedHeaders);
|
||||
}
|
||||
|
||||
|
||||
public KafkaTestBinder(KafkaTestSupport kafkaTestSupport,
|
||||
KafkaMessageChannelBinder.Mode mode) {
|
||||
|
||||
try {
|
||||
ZookeeperConnect zookeeperConnect = new ZookeeperConnect();
|
||||
@@ -60,8 +54,6 @@ public class KafkaTestBinder extends AbstractTestBinder<KafkaMessageChannelBinde
|
||||
kafkaTestSupport.getBrokerAddress(),
|
||||
kafkaTestSupport.getZkConnectString());
|
||||
binder.setCodec(getCodec());
|
||||
binder.setDefaultBatchingEnabled(false);
|
||||
binder.setMode(mode);
|
||||
ProducerListener producerListener = new LoggingProducerListener();
|
||||
binder.setProducerListener(producerListener);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
|
||||
@@ -25,14 +25,11 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
@@ -51,39 +48,35 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
*/
|
||||
public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
@Override
|
||||
protected KafkaTestBinder createKafkaTestBinder() {
|
||||
return new KafkaTestBinder(kafkaTestSupport, KafkaMessageChannelBinder.Mode.raw);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport");
|
||||
properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
KafkaTestBinder binder = getBinder();
|
||||
KafkaProducerProperties properties = new KafkaProducerProperties();
|
||||
properties.setPartitionKeyExtractorClass(RawKafkaPartitionTestSupport.class);
|
||||
properties.setPartitionSelectorClass(RawKafkaPartitionTestSupport.class);
|
||||
properties.setPartitionCount(3);
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partJ.0", output, properties);
|
||||
properties.clear();
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("count","3");
|
||||
properties.put("partitionIndex", "0");
|
||||
|
||||
KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setPartitioned(true);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0J");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1J");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2J");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("partJ.0", "test", input2, properties);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("partJ.0", "test", input2, consumerProperties);
|
||||
|
||||
output.send(new GenericMessage<>(new byte[]{(byte)0}));
|
||||
output.send(new GenericMessage<>(new byte[]{(byte)1}));
|
||||
@@ -111,12 +104,11 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "payload[0]");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
KafkaTestBinder binder = getBinder();
|
||||
KafkaProducerProperties properties = new KafkaProducerProperties();
|
||||
properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload[0]"));
|
||||
properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
|
||||
properties.setPartitionCount(3);
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
@@ -129,21 +121,22 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
}
|
||||
|
||||
properties.clear();
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("partitionIndex", "0");
|
||||
properties.put("count","3");
|
||||
KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setPartitioned(true);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2S");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("part.0", "test", input2, properties);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("part.0", "test", input2, consumerProperties);
|
||||
|
||||
Message<byte[]> message2 = MessageBuilder.withPayload(new byte[]{2})
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo")
|
||||
@@ -177,11 +170,11 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@Test
|
||||
@Override
|
||||
public void testSendAndReceive() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, new KafkaProducerProperties());
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, new KafkaConsumerProperties());
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -203,20 +196,20 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveWithExplicitConsumerGroup() {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
// Test pub/sub by emulating how StreamPlugin handles taps
|
||||
QueueChannel module1InputChannel = new QueueChannel();
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, new KafkaProducerProperties());
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel, new KafkaConsumerProperties());
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = "baz.0";
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel, null);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel, new KafkaConsumerProperties());
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
Binding<MessageChannel> input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null);
|
||||
Binding<MessageChannel> input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, new KafkaConsumerProperties());
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
boolean success = false;
|
||||
@@ -252,7 +245,7 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
assertNull(receive(module3InputChannel));
|
||||
|
||||
// re-subscribed tap does receive the message
|
||||
input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null);
|
||||
input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, new KafkaConsumerProperties());
|
||||
assertNotNull(receive(module3InputChannel));
|
||||
|
||||
// clean up
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.rabbit;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitConsumerProperties extends ConsumerProperties {
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private boolean transacted = false;
|
||||
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
private int maxConcurrency = 1;
|
||||
|
||||
private int prefetch = 1;
|
||||
|
||||
private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
|
||||
|
||||
private int txSize = 1;
|
||||
|
||||
private boolean autoBindDlq = false;
|
||||
|
||||
private boolean durableSubscription = true;
|
||||
|
||||
private boolean republishToDlq = false;
|
||||
|
||||
private boolean requeueRejected = true;
|
||||
|
||||
private String replyHeaderPatterns;
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public boolean isTransacted() {
|
||||
return transacted;
|
||||
}
|
||||
|
||||
public void setTransacted(boolean transacted) {
|
||||
this.transacted = transacted;
|
||||
}
|
||||
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
Assert.notNull("Acknowledge mode cannot be null");
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
public int getMaxConcurrency() {
|
||||
return maxConcurrency;
|
||||
}
|
||||
|
||||
public void setMaxConcurrency(int maxConcurrency) {
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
public int getPrefetch() {
|
||||
return prefetch;
|
||||
}
|
||||
|
||||
public void setPrefetch(int prefetch) {
|
||||
this.prefetch = prefetch;
|
||||
}
|
||||
|
||||
public String[] getRequestHeaderPatterns() {
|
||||
return requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
|
||||
this.requestHeaderPatterns = requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public int getTxSize() {
|
||||
return txSize;
|
||||
}
|
||||
|
||||
public void setTxSize(int txSize) {
|
||||
this.txSize = txSize;
|
||||
}
|
||||
|
||||
public boolean isAutoBindDlq() {
|
||||
return autoBindDlq;
|
||||
}
|
||||
|
||||
public void setAutoBindDlq(boolean autoBindDlq) {
|
||||
this.autoBindDlq = autoBindDlq;
|
||||
}
|
||||
|
||||
public boolean isDurableSubscription() {
|
||||
return durableSubscription;
|
||||
}
|
||||
|
||||
public void setDurableSubscription(boolean durableSubscription) {
|
||||
this.durableSubscription = durableSubscription;
|
||||
}
|
||||
|
||||
public boolean isRepublishToDlq() {
|
||||
return republishToDlq;
|
||||
}
|
||||
|
||||
public void setRepublishToDlq(boolean republishToDlq) {
|
||||
this.republishToDlq = republishToDlq;
|
||||
}
|
||||
|
||||
public boolean isRequeueRejected() {
|
||||
return requeueRejected;
|
||||
}
|
||||
|
||||
public void setRequeueRejected(boolean requeueRejected) {
|
||||
this.requeueRejected = requeueRejected;
|
||||
}
|
||||
|
||||
public String getReplyHeaderPatterns() {
|
||||
return replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setReplyHeaderPatterns(String replyHeaderPatterns) {
|
||||
this.replyHeaderPatterns = replyHeaderPatterns;
|
||||
}
|
||||
}
|
||||
@@ -19,25 +19,23 @@ package org.springframework.cloud.stream.binder.rabbit;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import com.rabbitmq.client.AMQP;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Envelope;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.amqp.AmqpConnectException;
|
||||
import org.springframework.amqp.UncategorizedAmqpException;
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.core.AnonymousQueue;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.DirectExchange;
|
||||
import org.springframework.amqp.core.Exchange;
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.amqp.core.MessagePostProcessor;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
@@ -62,17 +60,14 @@ import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
import org.springframework.cloud.stream.binder.PartitionHandler;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
|
||||
@@ -94,10 +89,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.rabbitmq.client.AMQP;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Envelope;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ.
|
||||
*
|
||||
@@ -109,86 +100,13 @@ import com.rabbitmq.client.Envelope;
|
||||
* @author David Turanski
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel, RabbitConsumerProperties, RabbitProducerProperties> {
|
||||
|
||||
public static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR
|
||||
= new AnonymousQueue.Base64UrlNamingStrategy("anonymous.");
|
||||
|
||||
private static final AcknowledgeMode DEFAULT_ACKNOWLEDGE_MODE = AcknowledgeMode.AUTO;
|
||||
|
||||
private static final MessageDeliveryMode DEFAULT_DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT;
|
||||
|
||||
private static final boolean DEFAULT_DEFAULT_REQUEUE_REJECTED = true;
|
||||
|
||||
private static final int DEFAULT_MAX_CONCURRENCY = 1;
|
||||
|
||||
private static final int DEFAULT_PREFETCH_COUNT = 1;
|
||||
|
||||
static final String DEFAULT_RABBIT_PREFIX = "binder.";
|
||||
|
||||
private static final int DEFAULT_TX_SIZE = 1;
|
||||
|
||||
private static final String[] DEFAULT_REQUEST_HEADER_PATTERNS = new String[] { "STANDARD_REQUEST_HEADERS", "*" };
|
||||
|
||||
private static final String[] DEFAULT_REPLY_HEADER_PATTERNS = new String[] { "STANDARD_REPLY_HEADERS", "*" };
|
||||
|
||||
private static final String DEAD_LETTER_EXCHANGE = "DLX";
|
||||
|
||||
private static final Set<Object> RABBIT_CONSUMER_PROPERTIES = new HashSet<Object>(Arrays.asList(new String[] {
|
||||
BinderPropertyKeys.MAX_CONCURRENCY,
|
||||
RabbitPropertiesAccessor.ACK_MODE,
|
||||
RabbitPropertiesAccessor.PREFETCH,
|
||||
RabbitPropertiesAccessor.PREFIX,
|
||||
RabbitPropertiesAccessor.REQUEST_HEADER_PATTERNS,
|
||||
RabbitPropertiesAccessor.REQUEUE,
|
||||
RabbitPropertiesAccessor.TRANSACTED,
|
||||
RabbitPropertiesAccessor.TX_SIZE,
|
||||
RabbitPropertiesAccessor.AUTO_BIND_DLQ,
|
||||
RabbitPropertiesAccessor.REPUBLISH_TO_DLQ,
|
||||
RabbitPropertiesAccessor.DURABLE
|
||||
}));
|
||||
|
||||
/**
|
||||
* Standard + retry + rabbit consumer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_BASIC_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.addAll(RABBIT_CONSUMER_PROPERTIES)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + durable + concurrency + partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Rabbit producer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_BASIC_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.add(RabbitPropertiesAccessor.DELIVERY_MODE)
|
||||
.add(RabbitPropertiesAccessor.PREFIX)
|
||||
.add(RabbitPropertiesAccessor.REQUEST_HEADER_PATTERNS)
|
||||
.add(BinderPropertyKeys.COMPRESS)
|
||||
.add(BinderPropertyKeys.REQUIRED_GROUPS)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Partitioning + rabbit producer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_ADVANCED_PROPERTIES)
|
||||
.add(RabbitPropertiesAccessor.AUTO_BIND_DLQ)
|
||||
.build();
|
||||
|
||||
private static final MessagePropertiesConverter inboundMessagePropertiesConverter =
|
||||
new DefaultMessagePropertiesConverter() {
|
||||
|
||||
@@ -217,34 +135,6 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private MessagePostProcessor compressingPostProcessor = new GZipPostProcessor();
|
||||
|
||||
// Default RabbitMQ Container properties
|
||||
|
||||
private volatile AcknowledgeMode defaultAcknowledgeMode = DEFAULT_ACKNOWLEDGE_MODE;
|
||||
|
||||
private volatile boolean defaultChannelTransacted;
|
||||
|
||||
private volatile MessageDeliveryMode defaultDefaultDeliveryMode = DEFAULT_DEFAULT_DELIVERY_MODE;
|
||||
|
||||
private volatile boolean defaultDefaultRequeueRejected = DEFAULT_DEFAULT_REQUEUE_REJECTED;
|
||||
|
||||
private volatile int defaultMaxConcurrency = DEFAULT_MAX_CONCURRENCY;
|
||||
|
||||
private volatile int defaultPrefetchCount = DEFAULT_PREFETCH_COUNT;
|
||||
|
||||
private volatile int defaultTxSize = DEFAULT_TX_SIZE;
|
||||
|
||||
protected volatile boolean defaultDurableSubscription = true;
|
||||
|
||||
private volatile String defaultPrefix = DEFAULT_RABBIT_PREFIX;
|
||||
|
||||
private volatile String[] defaultRequestHeaderPatterns = DEFAULT_REQUEST_HEADER_PATTERNS;
|
||||
|
||||
private volatile String[] defaultReplyHeaderPatterns = DEFAULT_REPLY_HEADER_PATTERNS;
|
||||
|
||||
private volatile boolean defaultAutoBindDLQ = false;
|
||||
|
||||
private volatile boolean defaultRepublishToDLQ = false;
|
||||
|
||||
private volatile String[] addresses;
|
||||
|
||||
private volatile String[] adminAddresses;
|
||||
@@ -293,71 +183,6 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
this.compressingPostProcessor = compressingPostProcessor;
|
||||
}
|
||||
|
||||
public void setDefaultAcknowledgeMode(AcknowledgeMode defaultAcknowledgeMode) {
|
||||
Assert.notNull(defaultAcknowledgeMode, "'defaultAcknowledgeMode' cannot be null");
|
||||
this.defaultAcknowledgeMode = defaultAcknowledgeMode;
|
||||
}
|
||||
|
||||
public void setDefaultChannelTransacted(boolean defaultChannelTransacted) {
|
||||
this.defaultChannelTransacted = defaultChannelTransacted;
|
||||
}
|
||||
|
||||
public void setDefaultDefaultDeliveryMode(MessageDeliveryMode defaultDefaultDeliveryMode) {
|
||||
Assert.notNull(defaultDefaultDeliveryMode, "'defaultDeliveryMode' cannot be null");
|
||||
this.defaultDefaultDeliveryMode = defaultDefaultDeliveryMode;
|
||||
}
|
||||
|
||||
public void setDefaultDefaultRequeueRejected(boolean defaultDefaultRequeueRejected) {
|
||||
this.defaultDefaultRequeueRejected = defaultDefaultRequeueRejected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the binder's default max consumers; can be overridden by consumer.maxConcurrency. Values less than 'concurrency'
|
||||
* will be coerced to be equal to concurrency.
|
||||
* @param defaultMaxConcurrency The default max concurrency.
|
||||
*/
|
||||
public void setDefaultMaxConcurrency(int defaultMaxConcurrency) {
|
||||
this.defaultMaxConcurrency = defaultMaxConcurrency;
|
||||
}
|
||||
|
||||
public void setDefaultPrefetchCount(int defaultPrefetchCount) {
|
||||
this.defaultPrefetchCount = defaultPrefetchCount;
|
||||
}
|
||||
|
||||
public void setDefaultTxSize(int defaultTxSize) {
|
||||
this.defaultTxSize = defaultTxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether subscriptions are durable.
|
||||
* @param defaultDurableSubscription true for durable (default false).
|
||||
*/
|
||||
public void setDefaultDurableSubscription(boolean defaultDurableSubscription) {
|
||||
this.defaultDurableSubscription = defaultDurableSubscription;
|
||||
}
|
||||
|
||||
|
||||
public void setDefaultPrefix(String defaultPrefix) {
|
||||
Assert.notNull(defaultPrefix, "'defaultPrefix' cannot be null");
|
||||
this.defaultPrefix = defaultPrefix.trim();
|
||||
}
|
||||
|
||||
public void setDefaultRequestHeaderPatterns(String[] defaultRequestHeaderPatterns) {
|
||||
this.defaultRequestHeaderPatterns = Arrays.copyOf(defaultRequestHeaderPatterns,
|
||||
defaultRequestHeaderPatterns.length);
|
||||
}
|
||||
|
||||
public void setDefaultReplyHeaderPatterns(String[] defaultReplyHeaderPatterns) {
|
||||
this.defaultReplyHeaderPatterns = Arrays.copyOf(defaultReplyHeaderPatterns, defaultReplyHeaderPatterns.length);
|
||||
}
|
||||
|
||||
public void setDefaultAutoBindDLQ(boolean defaultAutoBindDLQ) {
|
||||
this.defaultAutoBindDLQ = defaultAutoBindDLQ;
|
||||
}
|
||||
|
||||
public void setDefaultRepublishToDLQ(boolean defaultRepublishToDLQ) {
|
||||
this.defaultRepublishToDLQ = defaultRepublishToDLQ;
|
||||
}
|
||||
|
||||
public void setAddresses(String[] addresses) {
|
||||
this.addresses = Arrays.copyOf(addresses, addresses.length);
|
||||
@@ -405,23 +230,21 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, RabbitConsumerProperties properties) {
|
||||
boolean anonymousConsumer = !StringUtils.hasText(group);
|
||||
String baseQueueName = anonymousConsumer ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName())
|
||||
: groupedName(name, group);
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name);
|
||||
}
|
||||
validateConsumerProperties(baseQueueName, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String prefix = accessor.getPrefix(this.defaultPrefix);
|
||||
String prefix = properties.getPrefix();
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
|
||||
String queueName = applyPrefix(prefix, baseQueueName);
|
||||
boolean partitioned = !anonymousConsumer && accessor.getPartitionIndex() >= 0;
|
||||
boolean durable = !anonymousConsumer && accessor.isDurable(this.defaultDurableSubscription);
|
||||
boolean partitioned = !anonymousConsumer && properties.isPartitioned();
|
||||
boolean durable = !anonymousConsumer && properties.isDurableSubscription();
|
||||
Queue queue;
|
||||
|
||||
if (anonymousConsumer) {
|
||||
@@ -429,11 +252,11 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
else {
|
||||
if (partitioned) {
|
||||
String partitionSuffix = "-" + accessor.getPartitionIndex();
|
||||
String partitionSuffix = "-" + properties.getInstanceIndex();
|
||||
queueName += partitionSuffix;
|
||||
}
|
||||
if (durable) {
|
||||
queue = new Queue(queueName, true, false, false, queueArgs(accessor, queueName));
|
||||
queue = new Queue(queueName, true, false, false, queueArgs(queueName, properties.getPrefix(), properties.isAutoBindDlq()));
|
||||
}
|
||||
else {
|
||||
queue = new Queue(queueName, false, false, true);
|
||||
@@ -443,31 +266,30 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
declareQueue(queueName, queue);
|
||||
|
||||
if (partitioned) {
|
||||
String bindingKey = String.format("%s-%d", name, accessor.getPartitionIndex());
|
||||
String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
|
||||
}
|
||||
else {
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
|
||||
}
|
||||
Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, accessor);
|
||||
Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
|
||||
if (durable) {
|
||||
autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, accessor);
|
||||
autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, properties.getPrefix(), properties.isAutoBindDlq());
|
||||
}
|
||||
return binding;
|
||||
|
||||
}
|
||||
|
||||
private Map<String, Object> queueArgs(RabbitPropertiesAccessor accessor, String queueName) {
|
||||
private Map<String, Object> queueArgs(String queueName, String prefix, boolean bindDlq) {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
if (accessor.getAutoBindDLQ(this.defaultAutoBindDLQ)) {
|
||||
args.put("x-dead-letter-exchange", applyPrefix(accessor.getPrefix(this.defaultPrefix), "DLX"));
|
||||
if (bindDlq) {
|
||||
args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX"));
|
||||
args.put("x-dead-letter-routing-key", queueName);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
|
||||
final RabbitPropertiesAccessor properties) {
|
||||
final RabbitConsumerProperties properties) {
|
||||
DefaultBinding<MessageChannel> consumerBinding = null;
|
||||
// TODO https://github.com/spring-cloud/spring-cloud-stream/issues/401
|
||||
ClassLoader originalClassloader = Thread.currentThread().getContextClassLoader();
|
||||
@@ -475,31 +297,30 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
ClassUtils.overrideThreadContextClassLoader(SimpleMessageListenerContainer.class.getClassLoader());
|
||||
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
|
||||
this.connectionFactory);
|
||||
listenerContainer.setAcknowledgeMode(properties.getAcknowledgeMode(this.defaultAcknowledgeMode));
|
||||
listenerContainer.setChannelTransacted(properties.getTransacted(this.defaultChannelTransacted));
|
||||
listenerContainer.setDefaultRequeueRejected(properties.getRequeueRejected(this
|
||||
.defaultDefaultRequeueRejected));
|
||||
listenerContainer.setAcknowledgeMode(properties.getAcknowledgeMode());
|
||||
listenerContainer.setChannelTransacted(properties.isTransacted());
|
||||
listenerContainer.setDefaultRequeueRejected(properties.isRequeueRejected());
|
||||
|
||||
int concurrency = properties.getConcurrency(this.defaultConcurrency);
|
||||
int concurrency = properties.getConcurrency();
|
||||
concurrency = concurrency > 0 ? concurrency : 1;
|
||||
listenerContainer.setConcurrentConsumers(concurrency);
|
||||
int maxConcurrency = properties.getMaxConcurrency(this.defaultMaxConcurrency);
|
||||
int maxConcurrency = properties.getMaxConcurrency();
|
||||
if (maxConcurrency > concurrency) {
|
||||
listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
|
||||
}
|
||||
|
||||
listenerContainer.setPrefetchCount(properties.getPrefetchCount(this.defaultPrefetchCount));
|
||||
listenerContainer.setTxSize(properties.getTxSize(this.defaultTxSize));
|
||||
listenerContainer.setPrefetchCount(properties.getPrefetch());
|
||||
listenerContainer.setTxSize(properties.getTxSize());
|
||||
listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
|
||||
listenerContainer.setQueues(queue);
|
||||
int maxAttempts = properties.getMaxAttempts(this.defaultMaxAttempts);
|
||||
if (maxAttempts > 1 || properties.getRepublishToDLQ(this.defaultRepublishToDLQ)) {
|
||||
int maxAttempts = properties.getMaxAttempts();
|
||||
if (maxAttempts > 1 || properties.isRepublishToDlq()) {
|
||||
RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
|
||||
.maxAttempts(maxAttempts)
|
||||
.backOffOptions(properties.getBackOffInitialInterval(this.defaultBackOffInitialInterval),
|
||||
properties.getBackOffMultiplier(this.defaultBackOffMultiplier),
|
||||
properties.getBackOffMaxInterval(this.defaultBackOffMaxInterval))
|
||||
.recoverer(determineRecoverer(name, properties))
|
||||
.backOffOptions(properties.getBackOffInitialInterval(),
|
||||
properties.getBackOffMultiplier(),
|
||||
properties.getBackOffMaxInterval())
|
||||
.recoverer(determineRecoverer(name, properties.getPrefix(), properties.isRepublishToDlq()))
|
||||
.build();
|
||||
listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
|
||||
}
|
||||
@@ -514,15 +335,14 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
adapter.setOutputChannel(bridgeToModuleChannel);
|
||||
adapter.setBeanName("inbound." + name);
|
||||
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
|
||||
mapper.setRequestHeaderNames(properties.getRequestHeaderPattens(this.defaultRequestHeaderPatterns));
|
||||
mapper.setReplyHeaderNames(properties.getReplyHeaderPattens(this.defaultReplyHeaderPatterns));
|
||||
mapper.setRequestHeaderNames(properties.getRequestHeaderPatterns());
|
||||
mapper.setReplyHeaderNames(properties.getReplyHeaderPatterns());
|
||||
adapter.setHeaderMapper(mapper);
|
||||
adapter.afterPropertiesSet();
|
||||
consumerBinding = new DefaultBinding<MessageChannel>(name, group, moduleInputChannel, adapter, properties) {
|
||||
|
||||
consumerBinding = new DefaultBinding<MessageChannel>(name, group, moduleInputChannel, adapter) {
|
||||
@Override
|
||||
protected void afterUnbind() {
|
||||
cleanAutoDeclareContext(properties.getPrefix(defaultPrefix), name);
|
||||
cleanAutoDeclareContext(properties.getPrefix(), name);
|
||||
}
|
||||
};
|
||||
ReceivingHandler convertingBridge = new ReceivingHandler();
|
||||
@@ -538,10 +358,9 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
private MessageRecoverer determineRecoverer(String name, RabbitPropertiesAccessor properties) {
|
||||
if (properties.getRepublishToDLQ(this.defaultRepublishToDLQ)) {
|
||||
private MessageRecoverer determineRecoverer(String name, String prefix, boolean republish) {
|
||||
if (republish) {
|
||||
RabbitTemplate errorTemplate = new RabbitTemplate(this.connectionFactory);
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
RepublishMessageRecoverer republishMessageRecoverer = new RepublishMessageRecoverer(errorTemplate,
|
||||
deadLetterExchangeName(prefix),
|
||||
applyPrefix(prefix, name));
|
||||
@@ -552,40 +371,38 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
}
|
||||
|
||||
private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitPropertiesAccessor properties,
|
||||
RabbitTemplate rabbitTemplate) {
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitProducerProperties properties,
|
||||
RabbitTemplate rabbitTemplate) {
|
||||
String prefix = properties.getPrefix();
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
String partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = properties.getPartitionKeyExpression();
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
|
||||
endpoint.setExchangeName(exchange.getName());
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
if (!properties.isPartitioned()) {
|
||||
endpoint.setRoutingKey(name);
|
||||
}
|
||||
else {
|
||||
endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name)));
|
||||
}
|
||||
for (String requiredGroupName : properties.getRequiredGroups(defaultRequiredGroups)) {
|
||||
for (String requiredGroupName : properties.getRequiredGroups()) {
|
||||
String baseQueueName = exchangeName + "." + requiredGroupName;
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(properties, baseQueueName));
|
||||
if (!properties.isPartitioned()) {
|
||||
Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(baseQueueName, prefix, properties.isAutoBindDlq()));
|
||||
declareQueue(baseQueueName, queue);
|
||||
autoBindDLQ(baseQueueName, baseQueueName, properties);
|
||||
autoBindDLQ(baseQueueName, baseQueueName, properties.getPrefix(), properties.isAutoBindDlq());
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(name);
|
||||
declareBinding(baseQueueName, binding);
|
||||
}
|
||||
else {
|
||||
// if the stream is partitioned, create one queue for each target partition for the default group
|
||||
for (int i = 0; i < properties.getNextModuleCount(); i++) {
|
||||
for (int i = 0; i < properties.getPartitionCount(); i++) {
|
||||
String partitionSuffix = "-" + i;
|
||||
String partitionQueueName = baseQueueName + partitionSuffix;
|
||||
Queue queue = new Queue(partitionQueueName, true, false, false,
|
||||
queueArgs(properties, partitionQueueName));
|
||||
queueArgs(partitionQueueName, properties.getPrefix(), properties.isAutoBindDlq()));
|
||||
declareQueue(queue.getName(), queue);
|
||||
autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties);
|
||||
autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties.getPrefix(), properties.isAutoBindDlq());
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(name + partitionSuffix));
|
||||
}
|
||||
}
|
||||
@@ -594,40 +411,38 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private void configureOutboundHandler(AmqpOutboundEndpoint handler, RabbitPropertiesAccessor properties) {
|
||||
private void configureOutboundHandler(AmqpOutboundEndpoint handler, RabbitProducerProperties producerProperties) {
|
||||
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
|
||||
mapper.setRequestHeaderNames(properties.getRequestHeaderPattens(this.defaultRequestHeaderPatterns));
|
||||
mapper.setReplyHeaderNames(properties.getReplyHeaderPattens(this.defaultReplyHeaderPatterns));
|
||||
mapper.setRequestHeaderNames(producerProperties.getRequestHeaderPatterns());
|
||||
mapper.setReplyHeaderNames(producerProperties.getReplyHeaderPatterns());
|
||||
handler.setHeaderMapper(mapper);
|
||||
handler.setDefaultDeliveryMode(properties.getDeliveryMode(this.defaultDefaultDeliveryMode));
|
||||
handler.setDefaultDeliveryMode(producerProperties.getDeliveryMode());
|
||||
handler.setBeanFactory(this.getBeanFactory());
|
||||
handler.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outputChannel, Properties properties) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String exchangeName = applyPrefix(accessor.getPrefix(this.defaultPrefix), name);
|
||||
public Binding<MessageChannel> doBindProducer(String name, MessageChannel outputChannel, RabbitProducerProperties producerProperties) {
|
||||
String exchangeName = applyPrefix(producerProperties.getPrefix(), name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, accessor, determineRabbitTemplate(accessor));
|
||||
return doRegisterProducer(name, outputChannel, endpoint, accessor);
|
||||
AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, producerProperties, determineRabbitTemplate(producerProperties));
|
||||
return doRegisterProducer(name, outputChannel, endpoint, producerProperties);
|
||||
}
|
||||
|
||||
private RabbitTemplate determineRabbitTemplate(RabbitPropertiesAccessor properties) {
|
||||
private RabbitTemplate determineRabbitTemplate(RabbitProducerProperties properties) {
|
||||
RabbitTemplate rabbitTemplate = null;
|
||||
if (properties.isBatchingEnabled(this.defaultBatchingEnabled)) {
|
||||
if (properties.isBatchingEnabled()) {
|
||||
BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(
|
||||
properties.getBatchSize(this.defaultBatchSize),
|
||||
properties.geteBatchBufferLimit(this.defaultBatchBufferLimit),
|
||||
properties.getBatchTimeout(this.defaultBatchTimeout));
|
||||
properties.getBatchSize(),
|
||||
properties.getBatchBufferLimit(),
|
||||
properties.getBatchTimeout());
|
||||
rabbitTemplate = new BatchingRabbitTemplate(batchingStrategy,
|
||||
getApplicationContext().getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME,
|
||||
TaskScheduler.class));
|
||||
rabbitTemplate.setConnectionFactory(this.connectionFactory);
|
||||
}
|
||||
if (properties.isCompress(this.defaultCompress)) {
|
||||
if (properties.isCompress()) {
|
||||
if (rabbitTemplate == null) {
|
||||
rabbitTemplate = new RabbitTemplate(this.connectionFactory);
|
||||
}
|
||||
@@ -641,20 +456,19 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, RabbitPropertiesAccessor properties) {
|
||||
AmqpOutboundEndpoint delegate, RabbitProducerProperties properties) {
|
||||
return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, String replyTo, RabbitPropertiesAccessor properties) {
|
||||
AmqpOutboundEndpoint delegate, String replyTo, RabbitProducerProperties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
MessageHandler handler = new SendingHandler(delegate, replyTo, properties);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler);
|
||||
consumer.setBeanFactory(getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, properties);
|
||||
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
|
||||
consumer.start();
|
||||
return producerBinding;
|
||||
}
|
||||
@@ -664,15 +478,14 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
* queue name because we use default exchange routing by queue name for the original message.
|
||||
* @param queueName The base name for the queue (including the binder prefix, if any).
|
||||
* @param routingKey The routing key for the queue.
|
||||
* @param properties The properties accessor.
|
||||
* @param autoBindDlq true if the DLQ should be bound.
|
||||
*/
|
||||
private void autoBindDLQ(final String queueName, String routingKey, RabbitPropertiesAccessor properties) {
|
||||
private void autoBindDLQ(final String queueName, String routingKey, String prefix, boolean autoBindDlq) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("autoBindDLQ=" + properties.getAutoBindDLQ(this.defaultAutoBindDLQ)
|
||||
this.logger.debug("autoBindDLQ=" + autoBindDlq
|
||||
+ " for: " + queueName);
|
||||
}
|
||||
if (properties.getAutoBindDLQ(this.defaultAutoBindDLQ)) {
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
if (autoBindDlq) {
|
||||
String dlqName = constructDLQName(queueName);
|
||||
Queue dlq = new Queue(dlqName);
|
||||
declareQueue(dlqName, dlq);
|
||||
@@ -787,15 +600,18 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private final String replyTo;
|
||||
|
||||
private final RabbitProducerProperties producerProperties;
|
||||
|
||||
private final PartitionHandler partitionHandler;
|
||||
|
||||
private SendingHandler(MessageHandler delegate, String replyTo, RabbitPropertiesAccessor properties) {
|
||||
private SendingHandler(MessageHandler delegate, String replyTo, RabbitProducerProperties properties) {
|
||||
this.delegate = delegate;
|
||||
this.replyTo = replyTo;
|
||||
producerProperties = properties;
|
||||
ConfigurableListableBeanFactory beanFactory = RabbitMessageChannelBinder.this.getBeanFactory();
|
||||
this.setBeanFactory(beanFactory);
|
||||
this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector,
|
||||
properties, properties.getNextModuleCount());
|
||||
properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -805,7 +621,7 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
if (this.replyTo != null) {
|
||||
messageToSend.put(AmqpHeaders.REPLY_TO, this.replyTo);
|
||||
}
|
||||
if (this.partitionHandler.isPartitionedModule()) {
|
||||
if (producerProperties.isPartitioned()) {
|
||||
messageToSend.put(PARTITION_HEADER,
|
||||
this.partitionHandler.determinePartition(message));
|
||||
}
|
||||
@@ -862,136 +678,4 @@ public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Property accessor for the RabbitBinder. Refer to the Spring-AMQP documentation for information on the
|
||||
* specific properties.
|
||||
*/
|
||||
private static class RabbitPropertiesAccessor extends DefaultBindingPropertiesAccessor {
|
||||
|
||||
/**
|
||||
* The acknowledge mode (i.e. NONE, MANUAL, AUTO).
|
||||
*/
|
||||
private static final String ACK_MODE = "ackMode";
|
||||
|
||||
/**
|
||||
* The delivery mode (i.e. NON_PERSISTENT, PERSISTENT).
|
||||
*/
|
||||
private static final String DELIVERY_MODE = "deliveryMode";
|
||||
|
||||
/**
|
||||
* The prefetch count (basic qos).
|
||||
*/
|
||||
private static final String PREFETCH = "prefetch";
|
||||
|
||||
/**
|
||||
* The prefix for queues, exchanges.
|
||||
*/
|
||||
private static final String PREFIX = "prefix";
|
||||
|
||||
/**
|
||||
* The reply header patterns.
|
||||
*/
|
||||
private static final String REPLY_HEADER_PATTERNS = "replyHeaderPatterns";
|
||||
|
||||
/**
|
||||
* The request header patterns.
|
||||
*/
|
||||
private static final String REQUEST_HEADER_PATTERNS = "requestHeaderPatterns";
|
||||
|
||||
/**
|
||||
* Whether delivery failures should be requeued (boolean).
|
||||
*/
|
||||
private static final String REQUEUE = "requeue";
|
||||
|
||||
/**
|
||||
* Whether to use transacted channels (boolean).
|
||||
*/
|
||||
private static final String TRANSACTED = "transacted";
|
||||
|
||||
/**
|
||||
* The number of deliveries between acks.
|
||||
*/
|
||||
private static final String TX_SIZE = "txSize";
|
||||
|
||||
/**
|
||||
* Whether to automatically declare the DLQ and bind it to the binder DLX (boolean).
|
||||
*/
|
||||
private static final String AUTO_BIND_DLQ = "autoBindDLQ";
|
||||
|
||||
/**
|
||||
* Whether to automatically declare the DLQ and bind it to the binder DLX (boolean).
|
||||
*/
|
||||
private static final String REPUBLISH_TO_DLQ = "republishToDLQ";
|
||||
|
||||
/**
|
||||
* Durable pub/sub consumer.
|
||||
*/
|
||||
public static final String DURABLE = "durableSubscription";
|
||||
|
||||
public RabbitPropertiesAccessor(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public AcknowledgeMode getAcknowledgeMode(AcknowledgeMode defaultValue) {
|
||||
String ackknowledgeMode = getProperty(ACK_MODE);
|
||||
if (StringUtils.hasText(ackknowledgeMode)) {
|
||||
return AcknowledgeMode.valueOf(ackknowledgeMode);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDeliveryMode getDeliveryMode(MessageDeliveryMode defaultValue) {
|
||||
String deliveryMode = getProperty(DELIVERY_MODE);
|
||||
if (StringUtils.hasText(deliveryMode)) {
|
||||
return MessageDeliveryMode.valueOf(deliveryMode);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPrefetchCount(int defaultValue) {
|
||||
return getProperty(PREFETCH, defaultValue);
|
||||
}
|
||||
|
||||
public String getPrefix(String defaultValue) {
|
||||
return getProperty(PREFIX, defaultValue);
|
||||
}
|
||||
|
||||
public String[] getReplyHeaderPattens(String[] defaultValue) {
|
||||
return asStringArray(getProperty(REPLY_HEADER_PATTERNS), defaultValue);
|
||||
}
|
||||
|
||||
public String[] getRequestHeaderPattens(String[] defaultValue) {
|
||||
return asStringArray(getProperty(REQUEST_HEADER_PATTERNS), defaultValue);
|
||||
}
|
||||
|
||||
public boolean getRequeueRejected(boolean defaultValue) {
|
||||
return getProperty(REQUEUE, defaultValue);
|
||||
}
|
||||
|
||||
public boolean getTransacted(boolean defaultValue) {
|
||||
return getProperty(TRANSACTED, defaultValue);
|
||||
}
|
||||
|
||||
public int getTxSize(int defaultValue) {
|
||||
return getProperty(TX_SIZE, defaultValue);
|
||||
}
|
||||
|
||||
public boolean getAutoBindDLQ(boolean defaultValue) {
|
||||
return getProperty(AUTO_BIND_DLQ, defaultValue);
|
||||
}
|
||||
|
||||
public boolean getRepublishToDLQ(boolean defaultValue) {
|
||||
return getProperty(REPUBLISH_TO_DLQ, defaultValue);
|
||||
}
|
||||
|
||||
public boolean isDurable(boolean defaultValue) {
|
||||
return getProperty(DURABLE, defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.rabbit;
|
||||
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitProducerProperties extends ProducerProperties {
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
|
||||
|
||||
private boolean autoBindDlq = false;
|
||||
|
||||
private boolean compress = false;
|
||||
|
||||
private boolean batchingEnabled = false;
|
||||
|
||||
private int batchSize = 100;
|
||||
|
||||
private int batchBufferLimit = 10000;
|
||||
|
||||
private int batchTimeout = 5000;
|
||||
|
||||
private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT;
|
||||
|
||||
private String[] replyHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
|
||||
this.requestHeaderPatterns = requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public String[] getRequestHeaderPatterns() {
|
||||
return requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setAutoBindDlq(boolean autoBindDlq) {
|
||||
this.autoBindDlq = autoBindDlq;
|
||||
}
|
||||
|
||||
public boolean isAutoBindDlq() {
|
||||
return autoBindDlq;
|
||||
}
|
||||
|
||||
public void setCompress(boolean compress) {
|
||||
this.compress = compress;
|
||||
}
|
||||
|
||||
public boolean isCompress() {
|
||||
return compress;
|
||||
}
|
||||
|
||||
public void setDeliveryMode(MessageDeliveryMode deliveryMode) {
|
||||
this.deliveryMode = deliveryMode;
|
||||
}
|
||||
|
||||
public MessageDeliveryMode getDeliveryMode() {
|
||||
return deliveryMode;
|
||||
}
|
||||
|
||||
public String[] getReplyHeaderPatterns() {
|
||||
return replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setReplyHeaderPatterns(String[] replyHeaderPatterns) {
|
||||
this.replyHeaderPatterns = replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public boolean isBatchingEnabled() {
|
||||
return batchingEnabled;
|
||||
}
|
||||
|
||||
public void setBatchingEnabled(boolean batchingEnabled) {
|
||||
this.batchingEnabled = batchingEnabled;
|
||||
}
|
||||
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public int getBatchBufferLimit() {
|
||||
return batchBufferLimit;
|
||||
}
|
||||
|
||||
public void setBatchBufferLimit(int batchBufferLimit) {
|
||||
this.batchBufferLimit = batchBufferLimit;
|
||||
}
|
||||
|
||||
public int getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(int batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,238 +16,95 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.rabbit.config;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.rabbit.default")
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.rabbit")
|
||||
class RabbitBinderConfigurationProperties {
|
||||
|
||||
private AcknowledgeMode acknowledgeMode;
|
||||
private String[] addresses = new String[0];
|
||||
|
||||
private int backOffInitialInterval;
|
||||
private String[] adminAdresses = new String[0];
|
||||
|
||||
private int backOffMaxInterval;
|
||||
private String[] nodes = new String[0];
|
||||
|
||||
private double backOffMultiplier;
|
||||
private String username;
|
||||
|
||||
private boolean transacted;
|
||||
private String password;
|
||||
|
||||
private boolean concurrency;
|
||||
private String vhost;
|
||||
|
||||
private MessageDeliveryMode defaultDeliveryMode;
|
||||
private boolean useSSL;
|
||||
|
||||
private boolean defaultRequeueRejected;
|
||||
|
||||
private int maxAttempts;
|
||||
|
||||
private int maxConcurrency;
|
||||
|
||||
private int prefetchCount;
|
||||
|
||||
private String prefix;
|
||||
|
||||
private String[] replyHeaderPatterns;
|
||||
|
||||
private String[] requestHeaderPatterns;
|
||||
|
||||
private int txSize;
|
||||
|
||||
private boolean autoBindDLQ;
|
||||
|
||||
private boolean republishToDLQ;
|
||||
|
||||
private boolean batchingEnabled;
|
||||
|
||||
private int batchSize;
|
||||
|
||||
private int batchBufferLimit;
|
||||
|
||||
private int batchTimeout;
|
||||
|
||||
private boolean compress;
|
||||
private Resource sslPropertiesLocation;
|
||||
|
||||
private int compressionLevel;
|
||||
|
||||
private boolean durableSubscription = true;
|
||||
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return acknowledgeMode;
|
||||
public String[] getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
public void setAddresses(String[] addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public int getBackOffInitialInterval() {
|
||||
return backOffInitialInterval;
|
||||
public String[] getAdminAdresses() {
|
||||
return adminAdresses;
|
||||
}
|
||||
|
||||
public void setBackOffInitialInterval(int backOffInitialInterval) {
|
||||
this.backOffInitialInterval = backOffInitialInterval;
|
||||
public void setAdminAdresses(String[] adminAdresses) {
|
||||
this.adminAdresses = adminAdresses;
|
||||
}
|
||||
|
||||
public int getBackOffMaxInterval() {
|
||||
return backOffMaxInterval;
|
||||
public String[] getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void setBackOffMaxInterval(int backOffMaxInterval) {
|
||||
this.backOffMaxInterval = backOffMaxInterval;
|
||||
public void setNodes(String[] nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public double getBackOffMultiplier() {
|
||||
return backOffMultiplier;
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setBackOffMultiplier(double backOffMultiplier) {
|
||||
this.backOffMultiplier = backOffMultiplier;
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public boolean isTransacted() {
|
||||
return transacted;
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setTransacted(boolean transacted) {
|
||||
this.transacted = transacted;
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public boolean isConcurrency() {
|
||||
return concurrency;
|
||||
public String getVhost() {
|
||||
return vhost;
|
||||
}
|
||||
|
||||
public void setConcurrency(boolean concurrency) {
|
||||
this.concurrency = concurrency;
|
||||
public void setVhost(String vhost) {
|
||||
this.vhost = vhost;
|
||||
}
|
||||
|
||||
public MessageDeliveryMode getDefaultDeliveryMode() {
|
||||
return defaultDeliveryMode;
|
||||
public boolean isUseSSL() {
|
||||
return useSSL;
|
||||
}
|
||||
|
||||
public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) {
|
||||
this.defaultDeliveryMode = defaultDeliveryMode;
|
||||
public void setUseSSL(boolean useSSL) {
|
||||
this.useSSL = useSSL;
|
||||
}
|
||||
|
||||
public boolean isDefaultRequeueRejected() {
|
||||
return defaultRequeueRejected;
|
||||
public Resource getSslPropertiesLocation() {
|
||||
return sslPropertiesLocation;
|
||||
}
|
||||
|
||||
public void setDefaultRequeueRejected(boolean defaultRequeueRejected) {
|
||||
this.defaultRequeueRejected = defaultRequeueRejected;
|
||||
}
|
||||
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
}
|
||||
|
||||
public void setMaxAttempts(int maxAttempts) {
|
||||
this.maxAttempts = maxAttempts;
|
||||
}
|
||||
|
||||
public int getMaxConcurrency() {
|
||||
return maxConcurrency;
|
||||
}
|
||||
|
||||
public void setMaxConcurrency(int maxConcurrency) {
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
public int getPrefetchCount() {
|
||||
return prefetchCount;
|
||||
}
|
||||
|
||||
public void setPrefetchCount(int prefetchCount) {
|
||||
this.prefetchCount = prefetchCount;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public String[] getReplyHeaderPatterns() {
|
||||
return replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setReplyHeaderPatterns(String[] replyHeaderPatterns) {
|
||||
this.replyHeaderPatterns = replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public String[] getRequestHeaderPatterns() {
|
||||
return requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
|
||||
this.requestHeaderPatterns = requestHeaderPatterns;
|
||||
}
|
||||
|
||||
public int getTxSize() {
|
||||
return txSize;
|
||||
}
|
||||
|
||||
public void setTxSize(int txSize) {
|
||||
this.txSize = txSize;
|
||||
}
|
||||
|
||||
public boolean isAutoBindDLQ() {
|
||||
return autoBindDLQ;
|
||||
}
|
||||
|
||||
public void setAutoBindDLQ(boolean autoBindDLQ) {
|
||||
this.autoBindDLQ = autoBindDLQ;
|
||||
}
|
||||
|
||||
public boolean isRepublishToDLQ() {
|
||||
return republishToDLQ;
|
||||
}
|
||||
|
||||
public void setRepublishToDLQ(boolean republishToDLQ) {
|
||||
this.republishToDLQ = republishToDLQ;
|
||||
}
|
||||
|
||||
public boolean isBatchingEnabled() {
|
||||
return batchingEnabled;
|
||||
}
|
||||
|
||||
public void setBatchingEnabled(boolean batchingEnabled) {
|
||||
this.batchingEnabled = batchingEnabled;
|
||||
}
|
||||
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public int getBatchBufferLimit() {
|
||||
return batchBufferLimit;
|
||||
}
|
||||
|
||||
public void setBatchBufferLimit(int batchBufferLimit) {
|
||||
this.batchBufferLimit = batchBufferLimit;
|
||||
}
|
||||
|
||||
public int getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(int batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
|
||||
public boolean isCompress() {
|
||||
return compress;
|
||||
}
|
||||
|
||||
public void setCompress(boolean compress) {
|
||||
this.compress = compress;
|
||||
public void setSslPropertiesLocation(Resource sslPropertiesLocation) {
|
||||
this.sslPropertiesLocation = sslPropertiesLocation;
|
||||
}
|
||||
|
||||
public int getCompressionLevel() {
|
||||
@@ -257,12 +114,4 @@ class RabbitBinderConfigurationProperties {
|
||||
public void setCompressionLevel(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
}
|
||||
|
||||
public boolean isDurableSubscription() {
|
||||
return durableSubscription;
|
||||
}
|
||||
|
||||
public void setDurableSubscription(boolean durableSubscription) {
|
||||
this.durableSubscription = durableSubscription;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,63 +45,43 @@ public class RabbitMessageChannelBinderConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory rabbitConnectionFactory;
|
||||
|
||||
|
||||
@Autowired
|
||||
private RabbitBinderConfigurationProperties rabbitBinderConfigurationProperties;
|
||||
|
||||
@Autowired
|
||||
private SpringRabbitMQProperties springRabbitMQProperties;
|
||||
|
||||
@Bean
|
||||
RabbitMessageChannelBinder rabbitMessageChannelBinder() {
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(rabbitConnectionFactory);
|
||||
binder.setCodec(codec);
|
||||
binder.setAddresses(springRabbitMQProperties.getAddresses());
|
||||
binder.setAdminAddresses(springRabbitMQProperties.getAdminAdresses());
|
||||
binder.setAddresses(rabbitBinderConfigurationProperties.getAddresses());
|
||||
binder.setAdminAddresses(rabbitBinderConfigurationProperties.getAdminAdresses());
|
||||
binder.setCompressingPostProcessor(gZipPostProcessor());
|
||||
binder.setDecompressingPostProcessor(deCompressingPostProcessor());
|
||||
binder.setDefaultAcknowledgeMode(rabbitBinderConfigurationProperties.getAcknowledgeMode());
|
||||
binder.setDefaultAutoBindDLQ(rabbitBinderConfigurationProperties.isAutoBindDLQ());
|
||||
binder.setDefaultChannelTransacted(rabbitBinderConfigurationProperties.isTransacted());
|
||||
binder.setDefaultDefaultDeliveryMode(rabbitBinderConfigurationProperties.getDefaultDeliveryMode());
|
||||
binder.setDefaultDefaultRequeueRejected(rabbitBinderConfigurationProperties.isDefaultRequeueRejected());
|
||||
binder.setDefaultMaxConcurrency(rabbitBinderConfigurationProperties.getMaxConcurrency());
|
||||
binder.setDefaultPrefetchCount(rabbitBinderConfigurationProperties.getPrefetchCount());
|
||||
binder.setDefaultPrefix(rabbitBinderConfigurationProperties.getPrefix());
|
||||
binder.setDefaultReplyHeaderPatterns(rabbitBinderConfigurationProperties.getReplyHeaderPatterns());
|
||||
binder.setDefaultRepublishToDLQ(rabbitBinderConfigurationProperties.isRepublishToDLQ());
|
||||
binder.setDefaultRequestHeaderPatterns(rabbitBinderConfigurationProperties.getRequestHeaderPatterns());
|
||||
binder.setDefaultTxSize(rabbitBinderConfigurationProperties.getTxSize());
|
||||
binder.setNodes(springRabbitMQProperties.getNodes());
|
||||
binder.setPassword(springRabbitMQProperties.getPassword());
|
||||
binder.setSslPropertiesLocation(springRabbitMQProperties.getSslPropertiesLocation());
|
||||
binder.setUsername(springRabbitMQProperties.getUsername());
|
||||
binder.setUseSSL(springRabbitMQProperties.isUseSSL());
|
||||
binder.setVhost(springRabbitMQProperties.getVhost());
|
||||
binder.setDefaultDurableSubscription(rabbitBinderConfigurationProperties.isDurableSubscription());
|
||||
binder.setNodes(rabbitBinderConfigurationProperties.getNodes());
|
||||
binder.setPassword(rabbitBinderConfigurationProperties.getPassword());
|
||||
binder.setSslPropertiesLocation(rabbitBinderConfigurationProperties.getSslPropertiesLocation());
|
||||
binder.setUsername(rabbitBinderConfigurationProperties.getUsername());
|
||||
binder.setUseSSL(rabbitBinderConfigurationProperties.isUseSSL());
|
||||
binder.setVhost(rabbitBinderConfigurationProperties.getVhost());
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor deCompressingPostProcessor() {
|
||||
return new DelegatingDecompressingPostProcessor();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor gZipPostProcessor() {
|
||||
GZipPostProcessor gZipPostProcessor = new GZipPostProcessor();
|
||||
gZipPostProcessor.setLevel(rabbitBinderConfigurationProperties.getCompressionLevel());
|
||||
return gZipPostProcessor;
|
||||
return gZipPostProcessor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
ConnectionFactorySettings rabbitConnectionFactorySettings() {
|
||||
return new ConnectionFactorySettings();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringRabbitMQProperties springRabbitMQProperties() {
|
||||
return new SpringRabbitMQProperties();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/**
|
||||
* Bind to services, either locally or in a cloud environment.
|
||||
@@ -47,7 +46,6 @@ import org.springframework.context.annotation.PropertySource;
|
||||
@ConditionalOnMissingBean(Binder.class)
|
||||
@Import(RabbitMessageChannelBinderConfiguration.class)
|
||||
@AutoConfigureBefore({CloudAutoConfiguration.class, RabbitAutoConfiguration.class})
|
||||
@PropertySource("classpath:/META-INF/spring-cloud-stream/rabbit-binder.properties")
|
||||
public class RabbitServiceAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.rabbit.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.rabbitmq")
|
||||
class SpringRabbitMQProperties {
|
||||
|
||||
private String[] addresses = new String[0];
|
||||
|
||||
private String[] adminAdresses = new String[0];
|
||||
|
||||
private String[] nodes = new String[0];
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String vhost;
|
||||
|
||||
private boolean useSSL;
|
||||
|
||||
private Resource sslPropertiesLocation;
|
||||
|
||||
public String[] getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(String[] addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public String[] getAdminAdresses() {
|
||||
return adminAdresses;
|
||||
}
|
||||
|
||||
public void setAdminAdresses(String[] adminAdresses) {
|
||||
this.adminAdresses = adminAdresses;
|
||||
}
|
||||
|
||||
public String[] getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void setNodes(String[] nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getVhost() {
|
||||
return vhost;
|
||||
}
|
||||
|
||||
public void setVhost(String vhost) {
|
||||
this.vhost = vhost;
|
||||
}
|
||||
|
||||
public boolean isUseSSL() {
|
||||
return useSSL;
|
||||
}
|
||||
|
||||
public void setUseSSL(boolean useSSL) {
|
||||
this.useSSL = useSSL;
|
||||
}
|
||||
|
||||
public Resource getSslPropertiesLocation() {
|
||||
return sslPropertiesLocation;
|
||||
}
|
||||
|
||||
public void setSslPropertiesLocation(Resource sslPropertiesLocation) {
|
||||
this.sslPropertiesLocation = sslPropertiesLocation;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
spring.cloud.stream.binder.rabbit.default.acknowledgeMode: AUTO
|
||||
spring.cloud.stream.binder.rabbit.default.autoBindDLQ: false
|
||||
spring.cloud.stream.binder.rabbit.default.backOffInitialInterval: 1000
|
||||
spring.cloud.stream.binder.rabbit.default.backOffMaxInterval: 10000
|
||||
spring.cloud.stream.binder.rabbit.default.backOffMultiplier: 2.0
|
||||
spring.cloud.stream.binder.rabbit.default.batchBufferLimit: 10000
|
||||
spring.cloud.stream.binder.rabbit.default.batchingEnabled: false
|
||||
spring.cloud.stream.binder.rabbit.default.batchSize: 100
|
||||
spring.cloud.stream.binder.rabbit.default.batchTimeout: 5000
|
||||
spring.cloud.stream.binder.rabbit.default.compress: false
|
||||
spring.cloud.stream.binder.rabbit.default.concurrency: 1
|
||||
spring.cloud.stream.binder.rabbit.default.defaultDeliveryMode: PERSISTENT
|
||||
spring.cloud.stream.binder.rabbit.default.durableSubscription: false
|
||||
spring.cloud.stream.binder.rabbit.default.maxAttempts: 3
|
||||
spring.cloud.stream.binder.rabbit.default.maxConcurrency: 1
|
||||
spring.cloud.stream.binder.rabbit.default.prefix: binder.
|
||||
spring.cloud.stream.binder.rabbit.default.prefetch: 1
|
||||
spring.cloud.stream.binder.rabbit.default.replyHeaderPatterns: STANDARD_REPLY_HEADERS,*
|
||||
spring.cloud.stream.binder.rabbit.default.republishToDLQ: false
|
||||
spring.cloud.stream.binder.rabbit.default.requestHeaderPatterns: STANDARD_REQUEST_HEADERS,*
|
||||
spring.cloud.stream.binder.rabbit.default.defaultRequeueRejected: true
|
||||
spring.cloud.stream.binder.rabbit.default.transacted:false
|
||||
spring.cloud.stream.binder.rabbit.default.txSize: 1
|
||||
@@ -33,7 +33,6 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.Deflater;
|
||||
@@ -55,11 +54,11 @@ import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
|
||||
import org.springframework.amqp.utils.test.TestUtils;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy;
|
||||
import org.springframework.cloud.stream.binder.PartitionSelectorStrategy;
|
||||
import org.springframework.cloud.stream.binder.PartitionTestSupport;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -80,7 +79,7 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public class RabbitBinderTests extends PartitionCapableBinderTests<RabbitTestBinder, RabbitConsumerProperties, RabbitProducerProperties> {
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = RabbitMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
@@ -90,13 +89,23 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport();
|
||||
|
||||
@Override
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
protected RabbitTestBinder getBinder() {
|
||||
if (testBinder == null) {
|
||||
testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource());
|
||||
}
|
||||
return testBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RabbitConsumerProperties createConsumerProperties() {
|
||||
return new RabbitConsumerProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RabbitProducerProperties createProducerProperties() {
|
||||
return new RabbitProducerProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean usesExplicitRouting() {
|
||||
return true;
|
||||
@@ -104,11 +113,11 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveBad() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
RabbitTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bad.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bad.0", moduleOutputChannel, new RabbitProducerProperties());
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel, new RabbitConsumerProperties());
|
||||
Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
@@ -128,16 +137,16 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("transacted", "true"); // test transacted with defaults; not allowed with ackmode NONE
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setTransacted(true);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", null, new DirectChannel(), properties);
|
||||
AbstractEndpoint endpoint = extractEndpoint(consumerBinding);
|
||||
SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
assertEquals(AcknowledgeMode.AUTO, container.getAcknowledgeMode());
|
||||
assertThat(container.getQueueNames()[0],
|
||||
startsWith(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX));
|
||||
startsWith(properties.getPrefix()));
|
||||
assertTrue(TestUtils.getPropertyValue(container, "transactional", Boolean.class));
|
||||
assertEquals(1, TestUtils.getPropertyValue(container, "concurrentConsumers"));
|
||||
assertNull(TestUtils.getPropertyValue(container, "maxConcurrentConsumers"));
|
||||
@@ -152,20 +161,20 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
consumerBinding.unbind();
|
||||
assertFalse(endpoint.isRunning());
|
||||
|
||||
properties = new Properties();
|
||||
properties.put("ackMode", "NONE");
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
properties.put("backOffMaxInterval", "20000");
|
||||
properties.put("backOffMultiplier", "5.0");
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("maxAttempts", "23");
|
||||
properties.put("maxConcurrency", "3");
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("prefetch", "20");
|
||||
properties.put("requestHeaderPatterns", "foo");
|
||||
properties.put("requeue", "false");
|
||||
properties.put("txSize", "10");
|
||||
properties.put("partitionIndex", 0);
|
||||
properties = new RabbitConsumerProperties();
|
||||
properties.setAcknowledgeMode(AcknowledgeMode.NONE);
|
||||
properties.setBackOffInitialInterval(2000);
|
||||
properties.setBackOffMaxInterval(20000);
|
||||
properties.setBackOffMultiplier(5.0);
|
||||
properties.setConcurrency(2);
|
||||
properties.setMaxAttempts(23);
|
||||
properties.setMaxConcurrency(3);
|
||||
properties.setPrefix("foo.");
|
||||
properties.setPrefetch(20);
|
||||
properties.setRequestHeaderPatterns(new String[] {"foo"});
|
||||
properties.setRequeueRejected(false);
|
||||
properties.setTxSize(10);
|
||||
properties.setInstanceIndex(0);
|
||||
consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
|
||||
endpoint = extractEndpoint(consumerBinding);
|
||||
@@ -179,8 +188,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
RabbitTestBinder binder = getBinder();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), new RabbitProducerProperties());
|
||||
@SuppressWarnings("unchecked")
|
||||
AbstractEndpoint endpoint = extractEndpoint(producerBinding);
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
@@ -192,15 +201,15 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
producerBinding.unbind();
|
||||
assertFalse(endpoint.isRunning());
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
properties.put("requestHeaderPatterns", "foo");
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
RabbitProducerProperties properties = new RabbitProducerProperties();
|
||||
properties.setPrefix("foo.");
|
||||
properties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
|
||||
properties.setRequestHeaderPatterns(new String[] {"foo"});
|
||||
properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'"));
|
||||
properties.setPartitionKeyExtractorClass(TestPartitionKeyExtractorClass.class);
|
||||
properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0"));
|
||||
properties.setPartitionSelectorClass(TestPartitionSelectorClass.class);
|
||||
properties.setPartitionCount(1);
|
||||
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
endpoint = extractEndpoint(producerBinding);
|
||||
@@ -221,14 +230,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public void testDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
RabbitTestBinder binder = getBinder();
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("durableSubscription", "true");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setPrefix(TEST_PREFIX);
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setDurableSubscription(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.setRequeueRejected(false);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("durableTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
@@ -263,13 +272,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public void testNonDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("durableSubscription", "false");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setPrefix(TEST_PREFIX);
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setDurableSubscription(false);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.setRequeueRejected(false);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("nondurabletest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
@@ -288,13 +297,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQ() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
properties.put("durableSubscription","true");
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setPrefix(TEST_PREFIX);
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.setRequeueRejected(false);
|
||||
properties.setDurableSubscription(true);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("dlqTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
@@ -333,33 +342,34 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQPartionedConsumerFirst() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "bindertest.");
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
properties.put("partitionIndex", "0");
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setPrefix("bindertest.");
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.setRequeueRejected(false);
|
||||
properties.setPartitioned(true);
|
||||
properties.setInstanceIndex(0);
|
||||
DirectChannel input0 = new DirectChannel();
|
||||
input0.setBeanName("test.input0DLQ");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input0, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding1 =
|
||||
binder.bindConsumer("partDLQ.0", "default", new QueueChannel(), properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
properties.setInstanceIndex(1);
|
||||
DirectChannel input1 = new DirectChannel();
|
||||
input1.setBeanName("test.input1DLQ");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input1, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.0", "default", new QueueChannel(), properties);
|
||||
|
||||
properties.clear();
|
||||
properties.put("prefix", "bindertest.");
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "2");
|
||||
RabbitProducerProperties producerProperties = new RabbitProducerProperties();
|
||||
producerProperties.setPrefix("bindertest.");
|
||||
producerProperties.setAutoBindDlq(true);
|
||||
producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionCount(2);
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.0", output, properties);
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.0", output, producerProperties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
@@ -417,35 +427,36 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQPartionedProducerFirst() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
public void testAutoBindDLQPartitionedProducerFirst() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitProducerProperties properties = new RabbitProducerProperties();
|
||||
|
||||
properties.put("prefix", "bindertest.");
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("requiredGroups", "dlqPartGrp");
|
||||
properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "2");
|
||||
properties.setPrefix("bindertest.");
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setRequiredGroups("dlqPartGrp");
|
||||
properties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
|
||||
properties.setPartitionSelectorClass(PartitionTestSupport.class);
|
||||
properties.setPartitionCount(2);
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.1", output, properties);
|
||||
|
||||
properties.clear();
|
||||
properties.put("prefix", "bindertest.");
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
properties.put("partitionIndex", "0");
|
||||
RabbitConsumerProperties consumerProperties = new RabbitConsumerProperties();
|
||||
consumerProperties.setPrefix("bindertest.");
|
||||
consumerProperties.setAutoBindDlq(true);
|
||||
consumerProperties.setMaxAttempts(1); // disable retry
|
||||
consumerProperties.setRequeueRejected(false);
|
||||
consumerProperties.setPartitioned(true);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
DirectChannel input0 = new DirectChannel();
|
||||
input0.setBeanName("test.input0DLQ");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0, consumerProperties);
|
||||
Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
DirectChannel input1 = new DirectChannel();
|
||||
input1.setBeanName("test.input1DLQ");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), properties);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1, consumerProperties);
|
||||
Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), consumerProperties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
@@ -512,14 +523,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args);
|
||||
admin.declareQueue(queue);
|
||||
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
properties.put("republishToDLQ", "true");
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
properties.put("requeue", "false");
|
||||
properties.put("durableSubscription", "true");
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitConsumerProperties properties = new RabbitConsumerProperties();
|
||||
properties.setPrefix(TEST_PREFIX);
|
||||
properties.setAutoBindDlq(true);
|
||||
properties.setRepublishToDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.setRequeueRejected(false);
|
||||
properties.setDurableSubscription(true);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("dlqPubTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
@@ -554,21 +565,21 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
@Test
|
||||
public void testBatchingAndCompression() throws Exception {
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
properties.put("batchingEnabled", "true");
|
||||
properties.put("batchSize", "2");
|
||||
properties.put("batchBufferLimit", "100000");
|
||||
properties.put("batchTimeout", "30000");
|
||||
properties.put("compress", "true");
|
||||
properties.put("requiredGroups", "default");
|
||||
RabbitTestBinder binder = getBinder();
|
||||
RabbitProducerProperties properties = new RabbitProducerProperties();
|
||||
properties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
|
||||
properties.setBatchingEnabled(true);
|
||||
properties.setBatchSize(2);
|
||||
properties.setBatchBufferLimit(100000);
|
||||
properties.setBatchTimeout(30000);
|
||||
properties.setCompress(true);
|
||||
properties.setRequiredGroups("default");
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("batchingProducer");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("batching.0", output, properties);
|
||||
|
||||
while (template.receive(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "batching.0.default") != null) {
|
||||
while (template.receive(properties.getPrefix() + "batching.0.default") != null) {
|
||||
}
|
||||
|
||||
Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class));
|
||||
@@ -591,7 +602,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
QueueChannel input = new QueueChannel();
|
||||
input.setBeanName("batchingConsumer");
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("batching.0", "test", input, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("batching.0", "test", input, new RabbitConsumerProperties());
|
||||
|
||||
output.send(new GenericMessage<>("foo".getBytes()));
|
||||
output.send(new GenericMessage<>("bar".getBytes()));
|
||||
@@ -617,51 +628,56 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy();
|
||||
CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort());
|
||||
RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf);
|
||||
rabbitBinder.setDefaultAutoBindDLQ(true);
|
||||
AbstractTestBinder<RabbitMessageChannelBinder> binder = new RabbitTestBinder(cf, rabbitBinder);
|
||||
RabbitTestBinder binder = new RabbitTestBinder(cf, rabbitBinder);
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "latebinder.");
|
||||
RabbitProducerProperties properties = new RabbitProducerProperties();
|
||||
properties.setPrefix("latebinder.");
|
||||
properties.setAutoBindDlq(true);
|
||||
|
||||
MessageChannel moduleOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel, properties);
|
||||
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel, properties);
|
||||
RabbitConsumerProperties rabbitConsumerProperties = new RabbitConsumerProperties();
|
||||
rabbitConsumerProperties.setPrefix("latebinder.");
|
||||
Binding<MessageChannel> late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel, rabbitConsumerProperties);
|
||||
|
||||
properties.put("partitionKeyExpression", "payload.equals('0') ? 0 : 1");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
properties.put("nextModuleCount", "2");
|
||||
properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload.equals('0') ? 0 : 1"));
|
||||
properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
|
||||
properties.setPartitionCount(2);
|
||||
|
||||
MessageChannel partOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> partlate0ProducerBinding = binder.bindProducer("partlate.0", partOutputChannel, properties);
|
||||
|
||||
QueueChannel partInputChannel0 = new QueueChannel();
|
||||
QueueChannel partInputChannel1 = new QueueChannel();
|
||||
properties.clear();
|
||||
properties.put("prefix", "latebinder.");
|
||||
properties.put("partitionIndex", "0");
|
||||
Binding<MessageChannel> partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
Binding<MessageChannel> partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1, properties);
|
||||
|
||||
rabbitBinder.setDefaultAutoBindDLQ(false);
|
||||
properties.clear();
|
||||
properties.put("prefix", "latebinder.");
|
||||
RabbitConsumerProperties partLateConsumerProperties = new RabbitConsumerProperties();
|
||||
partLateConsumerProperties.setPrefix("latebinder.");
|
||||
partLateConsumerProperties.setPartitioned(true);
|
||||
partLateConsumerProperties.setInstanceIndex(0);
|
||||
Binding<MessageChannel> partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0, partLateConsumerProperties);
|
||||
partLateConsumerProperties.setInstanceIndex(1);
|
||||
Binding<MessageChannel> partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1, partLateConsumerProperties);
|
||||
|
||||
RabbitProducerProperties noDlqProducerProperties = new RabbitProducerProperties();
|
||||
noDlqProducerProperties.setPrefix("latebinder.");
|
||||
MessageChannel noDLQOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, properties);
|
||||
Binding<MessageChannel> noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, noDlqProducerProperties);
|
||||
|
||||
QueueChannel noDLQInputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel, properties);
|
||||
RabbitConsumerProperties noDlqConsumerProperties = new RabbitConsumerProperties();
|
||||
noDlqConsumerProperties.setPrefix("latebinder.");
|
||||
Binding<MessageChannel> noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel, noDlqConsumerProperties);
|
||||
|
||||
MessageChannel outputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel, properties);
|
||||
Binding<MessageChannel> pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel, noDlqProducerProperties);
|
||||
QueueChannel pubSubInputChannel = new QueueChannel();
|
||||
properties.setProperty("durableSubscription", "false");
|
||||
Binding<MessageChannel> nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup", pubSubInputChannel, properties);
|
||||
noDlqConsumerProperties.setDurableSubscription(false);
|
||||
Binding<MessageChannel> nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup", pubSubInputChannel, noDlqConsumerProperties);
|
||||
QueueChannel durablePubSubInputChannel = new QueueChannel();
|
||||
properties.setProperty("durableSubscription", "true");
|
||||
Binding<MessageChannel> durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup", durablePubSubInputChannel, properties);
|
||||
noDlqConsumerProperties.setDurableSubscription(true);
|
||||
Binding<MessageChannel> durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup", durablePubSubInputChannel, noDlqConsumerProperties);
|
||||
|
||||
proxy.start();
|
||||
|
||||
@@ -781,12 +797,12 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public Object receive(boolean expectNull) throws Exception {
|
||||
if (expectNull) {
|
||||
Thread.sleep(50);
|
||||
return template.receiveAndConvert(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + queue);
|
||||
return template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
|
||||
}
|
||||
Object bar = null;
|
||||
int n = 0;
|
||||
while (n++ < 100 && bar == null) {
|
||||
bar = template.receiveAndConvert(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + queue);
|
||||
bar = template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertTrue("Message did not arrive in RabbitMQ", n < 100);
|
||||
@@ -796,4 +812,20 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
};
|
||||
}
|
||||
|
||||
private static class TestPartitionKeyExtractorClass implements PartitionKeyExtractorStrategy {
|
||||
|
||||
@Override
|
||||
public Object extractKey(Message<?> message) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestPartitionSelectorClass implements PartitionSelectorStrategy {
|
||||
|
||||
@Override
|
||||
public int selectPartition(Object key, int partitionCount) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.stream.binder.rabbit;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
@@ -38,9 +37,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBinder> {
|
||||
|
||||
public static final String BINDER_PREFIX = "binder.";
|
||||
public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBinder, RabbitConsumerProperties, RabbitProducerProperties> {
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
@@ -68,32 +65,21 @@ public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBin
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, RabbitConsumerProperties properties) {
|
||||
if (group != null) {
|
||||
this.queues.add(prefix(properties) + name + ("." + group));
|
||||
this.queues.add(properties.getPrefix() + name + ("." + group));
|
||||
}
|
||||
this.exchanges.add(prefix(properties) + name);
|
||||
this.exchanges.add(properties.getPrefix() + name);
|
||||
return super.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
this.queues.add(prefix(properties) + name + ".default");
|
||||
this.exchanges.add(prefix(properties) + name);
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, RabbitProducerProperties properties) {
|
||||
this.queues.add(properties.getPrefix() + name + ".default");
|
||||
this.exchanges.add(properties.getPrefix() + name);
|
||||
return super.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
public String prefix(Properties properties) {
|
||||
if (properties != null) {
|
||||
String prefix = properties.getProperty("prefix");
|
||||
if (prefix != null) {
|
||||
this.prefixes.add(prefix);
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
return BINDER_PREFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
for (String queue : this.queues) {
|
||||
|
||||
@@ -82,7 +82,7 @@ public class RabbitBinderModuleTests {
|
||||
public void testParentConnectionFactoryInheritedByDefault() {
|
||||
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RabbitMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory =
|
||||
@@ -105,7 +105,7 @@ public class RabbitBinderModuleTests {
|
||||
public void testParentConnectionFactoryInheritedIfOverridden() {
|
||||
context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run("--server.port=0");
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RabbitMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory =
|
||||
@@ -135,7 +135,7 @@ public class RabbitBinderModuleTests {
|
||||
params.add("--server.port=0");
|
||||
context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[params.size()]));
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RabbitMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory =
|
||||
|
||||
@@ -21,24 +21,22 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
import org.springframework.cloud.stream.binder.PartitionHandler;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
@@ -68,7 +66,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author David Turanski
|
||||
* @author Jennifer Hickey
|
||||
*/
|
||||
public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private static final String ERROR_HEADER = "errorKey";
|
||||
|
||||
@@ -80,25 +78,6 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private final RedisOperations<String, String> redisOperations;
|
||||
|
||||
/**
|
||||
* Retry + concurrency + partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.add(BinderPropertyKeys.REQUIRED_GROUPS)
|
||||
.build();
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private final EmbeddedHeadersMessageConverter embeddedHeadersMessageConverter = new
|
||||
@@ -139,24 +118,21 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<MessageChannel> doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, ConsumerProperties properties) {
|
||||
if (!StringUtils.hasText(group)) {
|
||||
group = "anonymous." + UUID.randomUUID().toString();
|
||||
}
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
String queueName = groupedName(name, group);
|
||||
validateConsumerProperties(queueName, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
int partitionIndex = accessor.getPartitionIndex();
|
||||
if (partitionIndex >= 0) {
|
||||
queueName += "-" + partitionIndex;
|
||||
if (properties.isPartitioned()) {
|
||||
queueName += "-" + properties.getInstanceIndex();
|
||||
}
|
||||
MessageProducerSupport adapter = createInboundAdapter(accessor, queueName);
|
||||
return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, accessor);
|
||||
MessageProducerSupport adapter = createInboundAdapter(properties, queueName);
|
||||
return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, properties);
|
||||
}
|
||||
|
||||
private MessageProducerSupport createInboundAdapter(RedisPropertiesAccessor accessor, String queueName) {
|
||||
private MessageProducerSupport createInboundAdapter(ConsumerProperties accessor, String queueName) {
|
||||
MessageProducerSupport adapter;
|
||||
int concurrency = accessor.getConcurrency(this.defaultConcurrency);
|
||||
int concurrency = accessor.getConcurrency();
|
||||
concurrency = concurrency > 0 ? concurrency : 1;
|
||||
if (concurrency == 1) {
|
||||
RedisQueueMessageDrivenEndpoint single = new RedisQueueMessageDrivenEndpoint(queueName,
|
||||
@@ -172,7 +148,7 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterConsumer(String bindingName, String group, String channelName, MessageChannel moduleInputChannel,
|
||||
MessageProducerSupport adapter, final RedisPropertiesAccessor properties) {
|
||||
MessageProducerSupport adapter, final ConsumerProperties properties) {
|
||||
DirectChannel bridgeToModuleChannel = new DirectChannel();
|
||||
bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
|
||||
bridgeToModuleChannel.setBeanName(channelName + ".bridge");
|
||||
@@ -180,7 +156,7 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
adapter.setOutputChannel(bridgeInputChannel);
|
||||
adapter.setBeanName("inbound." + channelName);
|
||||
adapter.afterPropertiesSet();
|
||||
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<MessageChannel>(bindingName, group, moduleInputChannel, adapter, properties) {
|
||||
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<MessageChannel>(bindingName, group, moduleInputChannel, adapter) {
|
||||
|
||||
@Override
|
||||
protected void afterUnbind() {
|
||||
@@ -207,7 +183,7 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
* @return The channel, or a wrapper.
|
||||
*/
|
||||
private MessageChannel addRetryIfNeeded(final String name, final DirectChannel bridgeToModuleChannel,
|
||||
RedisPropertiesAccessor properties) {
|
||||
ConsumerProperties properties) {
|
||||
final RetryTemplate retryTemplate = buildRetryTemplateIfRetryEnabled(properties);
|
||||
if (retryTemplate == null) {
|
||||
return bridgeToModuleChannel;
|
||||
@@ -256,18 +232,14 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindProducer(final String name, MessageChannel moduleOutputChannel, ProducerProperties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
return doRegisterProducer(name, moduleOutputChannel, accessor);
|
||||
return doRegisterProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, RedisPropertiesAccessor accessor) {
|
||||
String partitionKeyExtractorClass = accessor.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = accessor.getPartitionKeyExpression();
|
||||
private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, ProducerProperties properties) {
|
||||
RedisQueueOutboundChannelAdapter queue;
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
if (!properties.isPartitioned()) {
|
||||
queue = new RedisQueueOutboundChannelAdapter(name, this.connectionFactory);
|
||||
}
|
||||
else {
|
||||
@@ -280,15 +252,17 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
return queue;
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel, RedisPropertiesAccessor properties) {
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
ProducerProperties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
MessageHandler handler = new SendingHandler(name, properties);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler);
|
||||
consumer.setBeanFactory(this.getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, properties);
|
||||
String[] requiredGroups = properties.getRequiredGroups(defaultRequiredGroups);
|
||||
DefaultBinding<MessageChannel> producerBinding =
|
||||
new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
|
||||
String[] requiredGroups = properties.getRequiredGroups();
|
||||
if (!ObjectUtils.isEmpty(requiredGroups)) {
|
||||
for (String group : requiredGroups) {
|
||||
this.redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + name).incrementScore(group, 1);
|
||||
@@ -302,19 +276,18 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
private final String bindingName;
|
||||
|
||||
private final RedisPropertiesAccessor accessor;
|
||||
private final ProducerProperties producerProperties;
|
||||
|
||||
private final Map<String, RedisQueueOutboundChannelAdapter> adapters = new HashMap<>();
|
||||
|
||||
private final PartitionHandler partitionHandler;
|
||||
|
||||
private SendingHandler(String bindingName, RedisPropertiesAccessor properties) {
|
||||
private SendingHandler(String bindingName, ProducerProperties producerProperties) {
|
||||
this.bindingName = bindingName;
|
||||
this.accessor = properties;
|
||||
this.producerProperties = producerProperties;
|
||||
ConfigurableListableBeanFactory beanFactory = RedisMessageChannelBinder.this.getBeanFactory();
|
||||
this.setBeanFactory(beanFactory);
|
||||
this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector,
|
||||
properties, properties.getNextModuleCount());
|
||||
this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, producerProperties);
|
||||
refreshChannelAdapters();
|
||||
}
|
||||
|
||||
@@ -322,7 +295,7 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
MessageValues transformed = serializePayloadIfNecessary(message);
|
||||
|
||||
if (this.partitionHandler.isPartitionedModule()) {
|
||||
if (producerProperties.isPartitioned()) {
|
||||
transformed.put(PARTITION_HEADER, this.partitionHandler.determinePartition(message));
|
||||
}
|
||||
|
||||
@@ -340,7 +313,7 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
for (String group : groups) {
|
||||
if (!adapters.containsKey(group)) {
|
||||
String channel = String.format("%s.%s", this.bindingName, group);
|
||||
adapters.put(group, createProducerEndpoint(channel, accessor));
|
||||
adapters.put(group, createProducerEndpoint(channel, producerProperties));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,14 +348,6 @@ public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
|
||||
}
|
||||
|
||||
private static class RedisPropertiesAccessor extends DefaultBindingPropertiesAccessor {
|
||||
|
||||
public RedisPropertiesAccessor(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides concurrency by creating a list of message-driven endpoints.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.cloud.stream.binder.redis.config;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.redis.RedisMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -32,7 +31,6 @@ import org.springframework.integration.codec.Codec;
|
||||
* @author David Turanski
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(RedisBinderConfigurationProperties.class)
|
||||
@Import({PropertyPlaceholderAutoConfiguration.class, KryoCodecAutoConfiguration.class})
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.redis")
|
||||
public class RedisMessageChannelBinderConfiguration {
|
||||
@@ -42,8 +40,6 @@ public class RedisMessageChannelBinderConfiguration {
|
||||
@Autowired
|
||||
private Codec codec;
|
||||
|
||||
@Autowired
|
||||
private RedisBinderConfigurationProperties redisBinderConfigurationProperties;
|
||||
|
||||
@Autowired
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
@@ -54,11 +50,6 @@ public class RedisMessageChannelBinderConfiguration {
|
||||
RedisMessageChannelBinder redisMessageChannelBinder = new RedisMessageChannelBinder(this.redisConnectionFactory,
|
||||
this.headers);
|
||||
redisMessageChannelBinder.setCodec(this.codec);
|
||||
redisMessageChannelBinder.setDefaultBackOffInitialInterval(this.redisBinderConfigurationProperties.getBackOffInitialInterval());
|
||||
redisMessageChannelBinder.setDefaultBackOffMaxInterval(this.redisBinderConfigurationProperties.getBackOffMaxInterval());
|
||||
redisMessageChannelBinder.setDefaultBackOffMultiplier(this.redisBinderConfigurationProperties.getBackOffMultiplier());
|
||||
redisMessageChannelBinder.setDefaultConcurrency(this.redisBinderConfigurationProperties.getConcurrency());
|
||||
redisMessageChannelBinder.setDefaultMaxAttempts(this.redisBinderConfigurationProperties.getMaxAttempts());
|
||||
return redisMessageChannelBinder;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,17 +30,16 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
@@ -61,7 +60,7 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
public class RedisBinderTests extends PartitionCapableBinderTests<RedisTestBinder, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = RedisMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
@@ -74,13 +73,23 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
new EmbeddedHeadersMessageConverter();
|
||||
|
||||
@Override
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
protected RedisTestBinder getBinder() {
|
||||
if (testBinder == null) {
|
||||
testBinder = new RedisTestBinder(redisAvailableRule.getResource());
|
||||
}
|
||||
return testBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConsumerProperties createConsumerProperties() {
|
||||
return new ConsumerProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProducerProperties createProducerProperties() {
|
||||
return new ProducerProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean usesExplicitRouting() {
|
||||
return true;
|
||||
@@ -88,24 +97,25 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
Binding<MessageChannel> binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
RedisTestBinder binder = getBinder();
|
||||
ConsumerProperties properties1 = new ConsumerProperties();
|
||||
properties1.setMaxAttempts(1);
|
||||
Binding<MessageChannel> binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties1);
|
||||
AbstractEndpoint endpoint = extractEndpoint(binding);
|
||||
assertThat(endpoint, instanceOf(RedisQueueMessageDrivenEndpoint.class));
|
||||
assertSame(DirectChannel.class, TestUtils.getPropertyValue(endpoint, "outputChannel").getClass());
|
||||
binding.unbind();
|
||||
assertFalse(endpoint.isRunning());
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
properties.put("backOffMaxInterval", "20000");
|
||||
properties.put("backOffMultiplier", "5.0");
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("maxAttempts", "23");
|
||||
properties.put("partitionIndex", 0);
|
||||
ConsumerProperties properties2 = new ConsumerProperties();
|
||||
properties2.setBackOffInitialInterval(2000);
|
||||
properties2.setBackOffMaxInterval(20000);
|
||||
properties2.setBackOffMultiplier(5.0);
|
||||
properties2.setConcurrency(2);
|
||||
properties2.setMaxAttempts(23);
|
||||
properties2.setInstanceIndex(0);
|
||||
|
||||
binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties2);
|
||||
endpoint = extractEndpoint(binding);
|
||||
verifyConsumer(endpoint);
|
||||
|
||||
@@ -115,9 +125,9 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
RedisTestBinder binder = getBinder();
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), createConsumerProperties());
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), createProducerProperties());
|
||||
AbstractEndpoint producerEndpoint = extractEndpoint(producerBinding);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, RedisQueueOutboundChannelAdapter> adapters = TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class);
|
||||
@@ -128,14 +138,14 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
producerBinding.unbind();
|
||||
assertFalse(producerEndpoint.isRunning());
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
ProducerProperties producerProperties = new ProducerProperties();
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'"));
|
||||
producerProperties.setPartitionKeyExtractorClass(AbstractRedisSerializerTests.Foo.class);
|
||||
producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0"));
|
||||
producerProperties.setPartitionSelectorClass(AbstractRedisSerializerTests.Foo.class);
|
||||
producerProperties.setPartitionCount(1);
|
||||
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), producerProperties);
|
||||
producerEndpoint = extractEndpoint(producerBinding);
|
||||
adapter = (RedisQueueOutboundChannelAdapter) TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class).get("test");
|
||||
assertEquals(
|
||||
@@ -168,15 +178,15 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRetryFail() {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
RedisTestBinder binder = getBinder();
|
||||
DirectChannel channel = new DirectChannel();
|
||||
binder.bindProducer("retry.0", channel, null);
|
||||
Properties props = new Properties();
|
||||
props.put("maxAttempts", 2);
|
||||
props.put("backOffInitialInterval", 100);
|
||||
props.put("backOffMultiplier", "1.0");
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), props); // no subscriber
|
||||
channel.send(new GenericMessage<String>("foo"));
|
||||
binder.bindProducer("retry.0", channel, createProducerProperties());
|
||||
ConsumerProperties consumerProperties = new ConsumerProperties();
|
||||
consumerProperties.setMaxAttempts(2);
|
||||
consumerProperties.setBackOffInitialInterval(100);
|
||||
consumerProperties.setBackOffMultiplier(1.0);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), consumerProperties); // no subscriber
|
||||
channel.send(new GenericMessage<>("foo"));
|
||||
RedisTemplate<String, Object> template = createTemplate();
|
||||
Object rightPop = template.boundListOps("ERRORS:retry.0.test").rightPop(5, TimeUnit.SECONDS);
|
||||
assertNotNull(rightPop);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.cloud.stream.binder.redis;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
@@ -34,7 +36,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class RedisTestBinder extends AbstractTestBinder<RedisMessageChannelBinder> {
|
||||
public class RedisTestBinder extends AbstractTestBinder<RedisMessageChannelBinder, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private StringRedisTemplate template;
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class RedisBinderModuleTests {
|
||||
public void testParentConnectionFactoryInheritedByDefault() {
|
||||
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
RedisConnectionFactory binderConnectionFactory =
|
||||
@@ -97,7 +97,7 @@ public class RedisBinderModuleTests {
|
||||
public void testParentConnectionFactoryInheritedIfOverridden() {
|
||||
context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run();
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
RedisConnectionFactory binderConnectionFactory =
|
||||
@@ -125,7 +125,7 @@ public class RedisBinderModuleTests {
|
||||
params.add("--spring.cloud.stream.binders.custom.environment.foo=bar");
|
||||
context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[]{}));
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?> binder = binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
RedisConnectionFactory binderConnectionFactory =
|
||||
|
||||
@@ -25,14 +25,11 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
@@ -49,11 +46,9 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractBinderTests {
|
||||
public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends AbstractBinder<MessageChannel, CP, PP>, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> {
|
||||
|
||||
protected static final Collection<MediaType> ALL = Collections.singletonList(MediaType.ALL);
|
||||
|
||||
protected AbstractTestBinder<?> testBinder;
|
||||
protected B testBinder;
|
||||
|
||||
/**
|
||||
* Subclasses may override this default value to have tests wait longer for a message receive, for example if
|
||||
@@ -72,12 +67,12 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testClean() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binding<MessageChannel> foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), null);
|
||||
Binding<MessageChannel> foo0ConsumerBinding = binder.bindConsumer("foo.0", "test", new DirectChannel(), null);
|
||||
Binding<MessageChannel> foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), null);
|
||||
Binding<MessageChannel> foo1ConsumerBinding = binder.bindConsumer("foo.1", "test", new DirectChannel(), null);
|
||||
Binding<MessageChannel> foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), null);
|
||||
Binder binder = getBinder();
|
||||
Binding<MessageChannel> foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), createProducerProperties());
|
||||
Binding<MessageChannel> foo0ConsumerBinding = binder.bindConsumer("foo.0", "test", new DirectChannel(), createConsumerProperties());
|
||||
Binding<MessageChannel> foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), createProducerProperties());
|
||||
Binding<MessageChannel> foo1ConsumerBinding = binder.bindConsumer("foo.1", "test", new DirectChannel(), createConsumerProperties());
|
||||
Binding<MessageChannel> foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), createProducerProperties());
|
||||
foo0ProducerBinding.unbind();
|
||||
assertFalse(TestUtils.getPropertyValue(foo0ProducerBinding, "endpoint", AbstractEndpoint.class).isRunning());
|
||||
foo0ConsumerBinding.unbind();
|
||||
@@ -92,11 +87,11 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceive() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, createProducerProperties());
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, createConsumerProperties());
|
||||
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
@@ -113,18 +108,18 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveMultipleTopics() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binder binder = getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel1 = new DirectChannel();
|
||||
DirectChannel moduleOutputChannel2 = new DirectChannel();
|
||||
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
|
||||
Binding<MessageChannel> producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, null);
|
||||
Binding<MessageChannel> producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, null);
|
||||
Binding<MessageChannel> producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, createProducerProperties());
|
||||
Binding<MessageChannel> producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, createProducerProperties());
|
||||
|
||||
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer("foo.x", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding2 = binder.bindConsumer("foo.y", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer("foo.x", "test", moduleInputChannel, createConsumerProperties());
|
||||
Binding<MessageChannel> consumerBinding2 = binder.bindConsumer("foo.y", "test", moduleInputChannel, createConsumerProperties());
|
||||
|
||||
String testPayload1 = "foo" + UUID.randomUUID().toString();
|
||||
Message<?> message1 = MessageBuilder.withPayload(testPayload1.getBytes()).build();
|
||||
@@ -157,11 +152,12 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveNoOriginalContentType() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Binder binder = getBinder();
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, createProducerProperties());
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, createConsumerProperties());
|
||||
binderBindUnbindLatency();
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("foo").build();
|
||||
@@ -176,7 +172,11 @@ public abstract class AbstractBinderTests {
|
||||
}
|
||||
|
||||
|
||||
protected abstract Binder<MessageChannel> getBinder() throws Exception;
|
||||
protected abstract B getBinder() throws Exception;
|
||||
|
||||
protected abstract CP createConsumerProperties();
|
||||
|
||||
protected abstract PP createProducerProperties();
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -29,7 +28,7 @@ import org.springframework.messaging.MessageChannel;
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractTestBinder<C extends AbstractBinder> implements Binder<MessageChannel> {
|
||||
public abstract class AbstractTestBinder<C extends AbstractBinder<MessageChannel, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> implements Binder<MessageChannel, CP, PP> {
|
||||
|
||||
protected Set<String> queues = new HashSet<String>();
|
||||
|
||||
@@ -46,13 +45,13 @@ public abstract class AbstractTestBinder<C extends AbstractBinder> implements Bi
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, CP properties) {
|
||||
queues.add(name);
|
||||
return binder.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, PP properties) {
|
||||
queues.add(name);
|
||||
return binder.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* Tests for binders that use an external broker.
|
||||
*
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public abstract class BrokerBinderTests extends AbstractBinderTests {
|
||||
public abstract class BrokerBinderTests<B extends AbstractTestBinder<? extends AbstractBinder<MessageChannel, CP, PP>, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> extends AbstractBinderTests<B,CP,PP> {
|
||||
|
||||
/**
|
||||
* Create a new spy on the given 'queue'. This allows de-correlating the creation of
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
@@ -28,15 +27,14 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hamcrest.CustomMatcher;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -53,21 +51,22 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
abstract public class PartitionCapableBinderTests<B extends AbstractTestBinder<? extends AbstractBinder<MessageChannel, CP, PP>, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> extends BrokerBinderTests<B,CP,PP> {
|
||||
|
||||
protected static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAnonymousGroup() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
B binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("defaultGroup.0", output, properties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("defaultGroup.0", output, createProducerProperties());
|
||||
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
Binding<MessageChannel> binding1 = binder.bindConsumer("defaultGroup.0", null, input1, properties);
|
||||
Binding<MessageChannel> binding1 = binder.bindConsumer("defaultGroup.0", null, input1, createConsumerProperties());
|
||||
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
Binding<MessageChannel> binding2 = binder.bindConsumer("defaultGroup.0", null, input2, properties);
|
||||
Binding<MessageChannel> binding2 = binder.bindConsumer("defaultGroup.0", null, input2, createConsumerProperties());
|
||||
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
@@ -85,7 +84,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload2.getBytes()));
|
||||
|
||||
binding2 = binder.bindConsumer("defaultGroup.0", null, input2, properties);
|
||||
binding2 = binder.bindConsumer("defaultGroup.0", null, input2, createConsumerProperties());
|
||||
String testPayload3 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload3.getBytes()));
|
||||
|
||||
@@ -107,21 +106,21 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testOneRequiredGroup() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
B binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
|
||||
PP producerProperties = createProducerProperties();
|
||||
|
||||
String testDestination = "testDestination" + UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
properties.put("requiredGroups", "test1");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testDestination, output, properties);
|
||||
producerProperties.setRequiredGroups("test1");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testDestination, output, producerProperties);
|
||||
|
||||
String testPayload = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload.getBytes()));
|
||||
|
||||
properties.clear();
|
||||
QueueChannel inbound1 = new QueueChannel();
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1, properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1, createConsumerProperties());
|
||||
|
||||
Message<?> receivedMessage1 = receive(inbound1);
|
||||
assertThat(receivedMessage1, not(nullValue()));
|
||||
@@ -133,23 +132,22 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testTwoRequiredGroups() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
B binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
Properties properties = new Properties();
|
||||
|
||||
String testDestination = "testDestination" + UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
properties.put("requiredGroups", "test1,test2");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testDestination, output, properties);
|
||||
PP producerProperties = createProducerProperties();
|
||||
producerProperties.setRequiredGroups("test1","test2");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testDestination, output, producerProperties);
|
||||
|
||||
String testPayload = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload.getBytes()));
|
||||
|
||||
properties.clear();
|
||||
QueueChannel inbound1 = new QueueChannel();
|
||||
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1, properties);
|
||||
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1, createConsumerProperties());
|
||||
QueueChannel inbound2 = new QueueChannel();
|
||||
Binding<MessageChannel> consumerBinding2 = binder.bindConsumer(testDestination, "test2", inbound2, properties);
|
||||
Binding<MessageChannel> consumerBinding2 = binder.bindConsumer(testDestination, "test2", inbound2, createConsumerProperties());
|
||||
|
||||
Message<?> receivedMessage1 = receive(inbound1);
|
||||
assertThat(receivedMessage1, not(nullValue()));
|
||||
@@ -163,60 +161,31 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("foo", "bar");
|
||||
properties.put("baz", "qux");
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
try {
|
||||
binder.bindProducer("badprops.0", output, properties);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(Matchers.containsString(getClassUnderTestName()
|
||||
+ " does not support producer "),
|
||||
containsString("foo"),
|
||||
containsString("baz"),
|
||||
containsString(" for badprops.0")));
|
||||
}
|
||||
|
||||
properties.remove("baz");
|
||||
try {
|
||||
binder.bindConsumer("badprops.0", "test", output, properties);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), equalTo(getClassUnderTestName()
|
||||
+ " does not support consumer property: foo for badprops.0.test."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
B binder = getBinder();
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put("concurrency", "2");
|
||||
consumerProperties.put("partitionIndex", "0");
|
||||
consumerProperties.put("count","3");
|
||||
CP consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setPartitioned(true);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "1");
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "2");
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2S");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("part.0", "test", input2, consumerProperties);
|
||||
|
||||
Properties producerProperties = new Properties();
|
||||
producerProperties.put("partitionKeyExpression", "payload");
|
||||
producerProperties.put("partitionSelectorExpression", "hashCode()");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
PP producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload"));
|
||||
producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
|
||||
producerProperties.setPartitionCount(3);
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
@@ -288,29 +257,29 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
B binder = getBinder();
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put("concurrency", "2");
|
||||
consumerProperties.put("count","3");
|
||||
consumerProperties.put("partitionIndex", "0");
|
||||
CP consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setPartitioned(true);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0J");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "1");
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1J");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "2");
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2J");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("partJ.0", "test", input2, consumerProperties);
|
||||
|
||||
Properties producerProperties = new Properties();
|
||||
producerProperties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
producerProperties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
producerProperties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
PP producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionCount(3);
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partJ.0", output, producerProperties);
|
||||
|
||||
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertSame;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -299,15 +298,15 @@ public class MessageChannelBinderSupportTests {
|
||||
|
||||
}
|
||||
|
||||
public class TestMessageChannelBinder extends AbstractBinder<MessageChannel> {
|
||||
public class TestMessageChannelBinder extends AbstractBinder<MessageChannel, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
@Override
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel channel, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel channel, ConsumerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel channel, Properties properties) {
|
||||
public Binding<MessageChannel> doBindProducer(String name, MessageChannel channel, ProducerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ public class MessageChannelConfigurerTests {
|
||||
String inputBindingProps = headerValue.get("input");
|
||||
assertTrue(inputBindingProps.contains("destination=configure"));
|
||||
assertTrue(inputBindingProps.contains("trackHistory=true"));
|
||||
assertTrue(inputBindingProps.contains("concurrency=1"));
|
||||
assertTrue(headerValue.get("instanceIndex").equals("0"));
|
||||
assertTrue(headerValue.get("instanceCount").equals("1"));
|
||||
latch.countDown();
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.cloud.stream.test.binder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -26,6 +25,8 @@ import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.test.matcher.MessageQueueMatcher;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -45,14 +46,14 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Fisher
|
||||
* @see MessageQueueMatcher
|
||||
*/
|
||||
public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
public class TestSupportBinder implements Binder<MessageChannel, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private final MessageCollectorImpl messageCollector = new MessageCollectorImpl();
|
||||
|
||||
private final ConcurrentMap<String, MessageChannel> messageChannels = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget, Properties properties) {
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget, ConsumerProperties properties) {
|
||||
return new TestBinding(inboundBindTarget, null);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
* Registers a single subscriber to the channel, that enqueues messages for later retrieval and assertion in tests.
|
||||
*/
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) {
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, ProducerProperties properties) {
|
||||
final BlockingQueue<Message<?>> queue = messageCollector.register(outboundBindTarget);
|
||||
((SubscribableChannel)outboundBindTarget).subscribe(new MessageHandler() {
|
||||
@Override
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.springframework.messaging.MessageChannel;
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class TestSupportBinderAutoConfiguration {
|
||||
|
||||
private Binder<MessageChannel> messageChannelBinder = new TestSupportBinder();
|
||||
private Binder<MessageChannel, ?, ?> messageChannelBinder = new TestSupportBinder();
|
||||
|
||||
@Bean
|
||||
public BinderFactory binderFactory() {
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.cloud.stream.config.BinderFactoryConfiguration;
|
||||
import org.springframework.cloud.stream.config.BindingBeansRegistrar;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceConfiguration;
|
||||
import org.springframework.cloud.stream.config.SpelExpressionConverterConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
@@ -43,7 +44,8 @@ import org.springframework.integration.config.EnableIntegration;
|
||||
@Documented
|
||||
@Inherited
|
||||
@Configuration
|
||||
@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class})
|
||||
@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class,
|
||||
SpelExpressionConverterConfiguration.class})
|
||||
@EnableIntegration
|
||||
public @interface EnableBinding {
|
||||
|
||||
|
||||
@@ -22,13 +22,8 @@ import static org.springframework.util.MimeTypeUtils.APPLICATION_OCTET_STREAM;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@@ -50,10 +45,8 @@ import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.AlternativeJdkIdGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -65,7 +58,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Fisher
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public abstract class AbstractBinder<T> implements ApplicationContextAware, InitializingBean, Binder<T> {
|
||||
public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends ProducerProperties> implements ApplicationContextAware, InitializingBean, Binder<T, C, P> {
|
||||
|
||||
protected static final String PARTITION_HEADER = "partition";
|
||||
|
||||
@@ -82,97 +75,10 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
|
||||
private final StringConvertingContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver();
|
||||
|
||||
private static final int DEFAULT_BACKOFF_INITIAL_INTERVAL = 1000;
|
||||
|
||||
private static final int DEFAULT_BACKOFF_MAX_INTERVAL = 10000;
|
||||
|
||||
private static final double DEFAULT_BACKOFF_MULTIPLIER = 2.0;
|
||||
|
||||
private static final int DEFAULT_CONCURRENCY = 1;
|
||||
|
||||
private static final int DEFAULT_MAX_ATTEMPTS = 3;
|
||||
|
||||
private static final int DEFAULT_BATCH_SIZE = 50;
|
||||
|
||||
private static final int DEFAULT_BATCH_BUFFER_LIMIT = 10000;
|
||||
|
||||
private static final int DEFAULT_BATCH_TIMEOUT = 0;
|
||||
|
||||
/**
|
||||
* The set of properties every binder implementation must support (or at least tolerate).
|
||||
*/
|
||||
|
||||
protected static final Set<Object> CONSUMER_STANDARD_PROPERTIES = new SetBuilder()
|
||||
.add(BinderPropertyKeys.COUNT)
|
||||
.add(BinderPropertyKeys.SEQUENCE)
|
||||
.build();
|
||||
|
||||
protected static final Set<Object> PRODUCER_STANDARD_PROPERTIES = new HashSet<Object>(Arrays.asList(
|
||||
BinderPropertyKeys.NEXT_MODULE_COUNT,
|
||||
BinderPropertyKeys.NEXT_MODULE_CONCURRENCY
|
||||
));
|
||||
|
||||
|
||||
protected static final Set<Object> CONSUMER_RETRY_PROPERTIES = new HashSet<Object>(Arrays.asList(new String[] {
|
||||
BinderPropertyKeys.BACK_OFF_INITIAL_INTERVAL,
|
||||
BinderPropertyKeys.BACK_OFF_MAX_INTERVAL,
|
||||
BinderPropertyKeys.BACK_OFF_MULTIPLIER,
|
||||
BinderPropertyKeys.MAX_ATTEMPTS
|
||||
}));
|
||||
|
||||
protected static final Set<Object> PRODUCER_PARTITIONING_PROPERTIES = new HashSet<Object>(
|
||||
Arrays.asList(new String[] {
|
||||
BinderPropertyKeys.PARTITION_KEY_EXPRESSION,
|
||||
BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS,
|
||||
BinderPropertyKeys.PARTITION_SELECTOR_CLASS,
|
||||
BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION,
|
||||
BinderPropertyKeys.MIN_PARTITION_COUNT
|
||||
}));
|
||||
|
||||
protected static final Set<Object> PRODUCER_BATCHING_BASIC_PROPERTIES = new HashSet<Object>(
|
||||
Arrays.asList(new String[] {
|
||||
BinderPropertyKeys.BATCHING_ENABLED,
|
||||
BinderPropertyKeys.BATCH_SIZE,
|
||||
BinderPropertyKeys.BATCH_TIMEOUT,
|
||||
}));
|
||||
|
||||
protected static final Set<Object> PRODUCER_BATCHING_ADVANCED_PROPERTIES = new HashSet<Object>(
|
||||
Arrays.asList(new String[] {
|
||||
BinderPropertyKeys.BATCH_BUFFER_LIMIT,
|
||||
}));
|
||||
|
||||
private final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
|
||||
|
||||
protected volatile EvaluationContext evaluationContext;
|
||||
|
||||
protected volatile PartitionSelectorStrategy partitionSelector;
|
||||
|
||||
protected volatile long defaultBackOffInitialInterval = DEFAULT_BACKOFF_INITIAL_INTERVAL;
|
||||
|
||||
protected volatile long defaultBackOffMaxInterval = DEFAULT_BACKOFF_MAX_INTERVAL;
|
||||
|
||||
protected volatile double defaultBackOffMultiplier = DEFAULT_BACKOFF_MULTIPLIER;
|
||||
|
||||
protected volatile int defaultConcurrency = DEFAULT_CONCURRENCY;
|
||||
|
||||
protected volatile int defaultMaxAttempts = DEFAULT_MAX_ATTEMPTS;
|
||||
|
||||
// properties for binder implementations that support batching
|
||||
|
||||
protected volatile boolean defaultBatchingEnabled = false;
|
||||
|
||||
protected volatile int defaultBatchSize = DEFAULT_BATCH_SIZE;
|
||||
|
||||
protected volatile int defaultBatchBufferLimit = DEFAULT_BATCH_BUFFER_LIMIT;
|
||||
|
||||
protected volatile long defaultBatchTimeout = DEFAULT_BATCH_TIMEOUT;
|
||||
|
||||
protected volatile String[] defaultRequiredGroups = new String[] {};
|
||||
|
||||
// compression
|
||||
|
||||
protected volatile boolean defaultCompress = false;
|
||||
|
||||
// Payload type cache
|
||||
private volatile Map<String, Class<?>> payloadTypeCache = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -212,110 +118,10 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
protected IdGenerator getIdGenerator() {
|
||||
return this.idGenerator;
|
||||
}
|
||||
|
||||
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the partition strategy to be used by this binder if no partitionExpression is provided for a module.
|
||||
* @param partitionSelector The selector.
|
||||
*/
|
||||
public void setPartitionSelector(PartitionSelectorStrategy partitionSelector) {
|
||||
this.partitionSelector = partitionSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default retry back off initial interval for this binder; can be overridden with consumer
|
||||
* 'backOffInitialInterval' property.
|
||||
* @param defaultBackOffInitialInterval
|
||||
*/
|
||||
public void setDefaultBackOffInitialInterval(long defaultBackOffInitialInterval) {
|
||||
this.defaultBackOffInitialInterval = defaultBackOffInitialInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default retry back off multiplier for this binder; can be overridden with consumer 'backOffMultiplier'
|
||||
* property.
|
||||
* @param defaultBackOffMultiplier
|
||||
*/
|
||||
public void setDefaultBackOffMultiplier(double defaultBackOffMultiplier) {
|
||||
this.defaultBackOffMultiplier = defaultBackOffMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default retry back off max interval for this binder; can be overridden with consumer
|
||||
* 'backOffMaxInterval'
|
||||
* property.
|
||||
* @param defaultBackOffMaxInterval
|
||||
*/
|
||||
public void setDefaultBackOffMaxInterval(long defaultBackOffMaxInterval) {
|
||||
this.defaultBackOffMaxInterval = defaultBackOffMaxInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default concurrency for this binder; can be overridden with consumer 'concurrency' property.
|
||||
* @param defaultConcurrency
|
||||
*/
|
||||
public void setDefaultConcurrency(int defaultConcurrency) {
|
||||
this.defaultConcurrency = defaultConcurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default maximum delivery attempts for this binder. Can be overridden by consumer property 'maxAttempts' if
|
||||
* supported. Values less than 2 disable retry and one delivery attempt is made.
|
||||
* @param defaultMaxAttempts The default maximum attempts.
|
||||
*/
|
||||
public void setDefaultMaxAttempts(int defaultMaxAttempts) {
|
||||
this.defaultMaxAttempts = defaultMaxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this binder batches message sends by default. Only applies to binder implementations that support
|
||||
* batching.
|
||||
* @param defaultBatchingEnabled the defaultBatchingEnabled to set.
|
||||
*/
|
||||
public void setDefaultBatchingEnabled(boolean defaultBatchingEnabled) {
|
||||
this.defaultBatchingEnabled = defaultBatchingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default batch size; only applies when batching is enabled and the binder supports batching.
|
||||
* @param defaultBatchSize the defaultBatchSize to set.
|
||||
*/
|
||||
public void setDefaultBatchSize(int defaultBatchSize) {
|
||||
this.defaultBatchSize = defaultBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default batch buffer limit - used to send a batch early if its size exceeds this. Only applies if
|
||||
* batching is enabled and the binder supports this property.
|
||||
* @param defaultBatchBufferLimit the defaultBatchBufferLimit to set.
|
||||
*/
|
||||
public void setDefaultBatchBufferLimit(int defaultBatchBufferLimit) {
|
||||
this.defaultBatchBufferLimit = defaultBatchBufferLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default batch timeout - used to send a batch if no messages arrive during this time. Only applies if
|
||||
* batching is enabled and the binder supports this property.
|
||||
* @param defaultBatchTimeout the defaultBatchTimeout to set.
|
||||
*/
|
||||
public void setDefaultBatchTimeout(long defaultBatchTimeout) {
|
||||
this.defaultBatchTimeout = defaultBatchTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether compression will be used by producers, by default.
|
||||
* @param defaultCompress 'true' to use compression.
|
||||
*/
|
||||
public void setDefaultCompress(boolean defaultCompress) {
|
||||
this.defaultCompress = defaultCompress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(this.applicationContext, "The 'applicationContext' property must not be null");
|
||||
@@ -334,16 +140,22 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Binding<T> bindConsumer(String name, String group, T target, Properties properties) {
|
||||
DefaultBindingPropertiesAccessor accessor = new DefaultBindingPropertiesAccessor(properties);
|
||||
public final Binding<T> bindConsumer(String name, String group, T target, C properties) {
|
||||
if (StringUtils.isEmpty(group)) {
|
||||
Assert.isTrue(accessor.getPartitionIndex() < 0,
|
||||
Assert.isTrue(!properties.isPartitioned(),
|
||||
"A consumer group is required for a partitioned subscription");
|
||||
}
|
||||
return doBindConsumer(name, group, target, properties);
|
||||
}
|
||||
|
||||
protected abstract Binding<T> doBindConsumer(String name, String group, T inputTarget, Properties properties);
|
||||
protected abstract Binding<T> doBindConsumer(String name, String group, T inputTarget, C properties);
|
||||
|
||||
@Override
|
||||
public final Binding<T> bindProducer(String name, T outboundBindTarget, P properties) {
|
||||
return doBindProducer(name, outboundBindTarget, properties);
|
||||
}
|
||||
|
||||
protected abstract Binding<T> doBindProducer(String name, T outboundBindTarget, P properties);
|
||||
|
||||
/**
|
||||
* Construct a name comprised of the name and group.
|
||||
@@ -458,57 +270,6 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the provided deployment properties for the consumer against those supported by this binder
|
||||
* implementation.
|
||||
* The consumer is that part of the binder that consumes messages from the underlying infrastructure and sends them
|
||||
* to
|
||||
* the next module. Consumer properties are used to configure the consumer.
|
||||
* @param name The name.
|
||||
* @param properties The properties.
|
||||
* @param supported The supported properties.
|
||||
*/
|
||||
protected void validateConsumerProperties(String name, Properties properties, Set<Object> supported) {
|
||||
if (properties != null) {
|
||||
validateProperties(name, properties, supported, "consumer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the provided deployment properties for the producer against those supported by this binder
|
||||
* implementation.
|
||||
* When a module sends a message to the binder, the producer uses these properties while sending it to the
|
||||
* underlying
|
||||
* infrastructure.
|
||||
* @param name The name.
|
||||
* @param properties The properties.
|
||||
* @param supported The supported properties.
|
||||
*/
|
||||
protected void validateProducerProperties(String name, Properties properties, Set<Object> supported) {
|
||||
if (properties != null) {
|
||||
validateProperties(name, properties, supported, "producer");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProperties(String name, Properties properties, Set<Object> supported, String type) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int errors = 0;
|
||||
for (Entry<Object, Object> entry : properties.entrySet()) {
|
||||
if (!supported.contains(entry.getKey())) {
|
||||
builder.append(entry.getKey()).append(",");
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if (errors > 0) {
|
||||
throw new IllegalArgumentException(getClass().getSimpleName() + " does not support "
|
||||
+ type
|
||||
+ " propert"
|
||||
+ (errors == 1 ? "y: " : "ies: ")
|
||||
+ builder.substring(0, builder.length() - 1)
|
||||
+ " for " + name + ".");
|
||||
}
|
||||
}
|
||||
|
||||
protected String buildPartitionRoutingExpression(String expressionRoot) {
|
||||
return "'" + expressionRoot + "-' + headers['" + PARTITION_HEADER + "']";
|
||||
}
|
||||
@@ -518,16 +279,16 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
* @param properties The properties.
|
||||
* @return The retry template, or null if retry is not enabled.
|
||||
*/
|
||||
protected RetryTemplate buildRetryTemplateIfRetryEnabled(DefaultBindingPropertiesAccessor properties) {
|
||||
int maxAttempts = properties.getMaxAttempts(this.defaultMaxAttempts);
|
||||
protected RetryTemplate buildRetryTemplateIfRetryEnabled(ConsumerProperties properties) {
|
||||
int maxAttempts = properties.getMaxAttempts();
|
||||
if (maxAttempts > 1) {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||
retryPolicy.setMaxAttempts(maxAttempts);
|
||||
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
|
||||
backOffPolicy.setInitialInterval(properties.getBackOffInitialInterval(this.defaultBackOffInitialInterval));
|
||||
backOffPolicy.setMultiplier(properties.getBackOffMultiplier(this.defaultBackOffMultiplier));
|
||||
backOffPolicy.setMaxInterval(properties.getBackOffMaxInterval(this.defaultBackOffMaxInterval));
|
||||
backOffPolicy.setInitialInterval(properties.getBackOffInitialInterval());
|
||||
backOffPolicy.setMultiplier(properties.getBackOffMultiplier());
|
||||
backOffPolicy.setMaxInterval(properties.getBackOffMaxInterval());
|
||||
template.setRetryPolicy(retryPolicy);
|
||||
template.setBackOffPolicy(backOffPolicy);
|
||||
return template;
|
||||
@@ -593,26 +354,6 @@ public abstract class AbstractBinder<T> implements ApplicationContextAware, Init
|
||||
|
||||
}
|
||||
|
||||
public static class SetBuilder {
|
||||
|
||||
private final Set<Object> set = new HashSet<Object>();
|
||||
|
||||
public SetBuilder add(Object o) {
|
||||
this.set.add(o);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SetBuilder addAll(Set<Object> set) {
|
||||
this.set.addAll(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<Object> build() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform manual acknowledgement based on the metadata stored in the binder.
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A strategy interface used to bind an app interface to a logical name. The name is intended to identify a
|
||||
* logical consumer or producer of messages. This may be a queue, a channel adapter, another message channel, a Spring
|
||||
@@ -28,9 +26,10 @@ import java.util.Properties;
|
||||
* @author Gary Russell
|
||||
* @author Jennifer Hickey
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Marius Bogoevici
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface Binder<T> {
|
||||
public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
|
||||
|
||||
/**
|
||||
* Bind the target component as a message consumer to the logical entity identified by the name.
|
||||
@@ -39,16 +38,16 @@ public interface Binder<T> {
|
||||
* in the same group (a <code>null</code> or empty String, must be treated as an anonymous group that doesn't share
|
||||
* the subscription with any other consumer)
|
||||
* @param inboundBindTarget the app interface to be bound as a consumer
|
||||
* @param properties arbitrary String key/value pairs that will be used as consumer properties in the binding
|
||||
* @param consumerProperties the consumer properties
|
||||
*/
|
||||
Binding<T> bindConsumer(String name, String group, T inboundBindTarget, Properties properties);
|
||||
Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
|
||||
|
||||
/**
|
||||
* Bind the target component as a message producer to the logical entity identified by the name.
|
||||
* @param name the logical identity of the message target
|
||||
* @param outboundBindTarget the app interface to be bound as a producer
|
||||
* @param properties arbitrary String key/value pairs that will be used as producer properties in the binding
|
||||
* @param producerProperties the producer properties
|
||||
*/
|
||||
Binding<T> bindProducer(String name, T outboundBindTarget, Properties properties);
|
||||
Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties);
|
||||
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ public interface BinderFactory<T> {
|
||||
* @param configurationName the name of a binder configuration
|
||||
* @return the binder instance
|
||||
*/
|
||||
Binder<T> getBinder(String configurationName);
|
||||
Binder<T, ?, ?> getBinder(String configurationName);
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
/**
|
||||
* Common binder properties.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public abstract class BinderPropertyKeys {
|
||||
|
||||
/**
|
||||
* The retry back off initial interval.
|
||||
*/
|
||||
public static final String BACK_OFF_INITIAL_INTERVAL = "backOffInitialInterval";
|
||||
|
||||
/**
|
||||
* The retry back off max interval.
|
||||
*/
|
||||
public static final String BACK_OFF_MAX_INTERVAL = "backOffMaxInterval";
|
||||
|
||||
/**
|
||||
* The retry back off multiplier.
|
||||
*/
|
||||
public static final String BACK_OFF_MULTIPLIER = "backOffMultiplier";
|
||||
|
||||
/**
|
||||
* The minimum number of concurrent deliveries.
|
||||
*/
|
||||
public static final String CONCURRENCY = "concurrency";
|
||||
|
||||
/**
|
||||
* The maximum delivery attempts when a delivery fails.
|
||||
*/
|
||||
public static final String MAX_ATTEMPTS = "maxAttempts";
|
||||
|
||||
/**
|
||||
* The maximum number of concurrent deliveries.
|
||||
*/
|
||||
public static final String MAX_CONCURRENCY = "maxConcurrency";
|
||||
|
||||
/**
|
||||
* The sequence index of the module.
|
||||
* In a partitioned stream, it is identical to the partition index.
|
||||
*/
|
||||
public static final String SEQUENCE = "sequence";
|
||||
|
||||
/**
|
||||
* The number of consumers, i.e. module instances in the stream.
|
||||
* In a partitioned stream, it is identical to the partition count.
|
||||
*/
|
||||
public static final String COUNT = "count";
|
||||
|
||||
/**
|
||||
* The consumer's partition index.
|
||||
*/
|
||||
public static final String PARTITION_INDEX = "partitionIndex";
|
||||
|
||||
/**
|
||||
* The partition key expression.
|
||||
*/
|
||||
public static final String PARTITION_KEY_EXPRESSION = "partitionKeyExpression";
|
||||
|
||||
/**
|
||||
* The partition key class.
|
||||
*/
|
||||
public static final String PARTITION_KEY_EXTRACTOR_CLASS = "partitionKeyExtractorClass";
|
||||
|
||||
/**
|
||||
* The partition selector class.
|
||||
*/
|
||||
public static final String PARTITION_SELECTOR_CLASS = "partitionSelectorClass";
|
||||
|
||||
/**
|
||||
* The partition selector expression.
|
||||
*/
|
||||
public static final String PARTITION_SELECTOR_EXPRESSION = "partitionSelectorExpression";
|
||||
|
||||
/**
|
||||
* True if message batching is enabled.
|
||||
*/
|
||||
public static final String BATCHING_ENABLED = "batchingEnabled";
|
||||
|
||||
/**
|
||||
* The batch size if batching is enabled.
|
||||
*/
|
||||
public static final String BATCH_SIZE = "batchSize";
|
||||
|
||||
/**
|
||||
* The buffer limit if batching is enabled.
|
||||
*/
|
||||
public static final String BATCH_BUFFER_LIMIT = "batchBufferLimit";
|
||||
|
||||
/**
|
||||
* The batch timeout if batching is enabled.
|
||||
*/
|
||||
public static final String BATCH_TIMEOUT = "batchTimeout";
|
||||
|
||||
/**
|
||||
* For all non-terminal modules, the number of modules coming after this one, irrespective of partitioning.
|
||||
*/
|
||||
public static final String NEXT_MODULE_COUNT = "nextModuleCount";
|
||||
|
||||
/**
|
||||
* For all non-terminal modules, the concurrency for module coming after this one.
|
||||
*/
|
||||
public static final String NEXT_MODULE_CONCURRENCY = "nextModuleConcurrency";
|
||||
|
||||
/**
|
||||
* Compression enabled.
|
||||
*/
|
||||
public static final String COMPRESS = "compress";
|
||||
|
||||
/**
|
||||
* Minimum partition count, if the transport supports partitioning natively (e.g. Kafka)
|
||||
*/
|
||||
public static final String MIN_PARTITION_COUNT = "minPartitionCount";
|
||||
|
||||
/**
|
||||
* Required groups. The binder will ensure that consumers from these groups that bind after
|
||||
* the producer will be able to receive messages produced in the mean time.
|
||||
*/
|
||||
public static final String REQUIRED_GROUPS = "requiredGroups";
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,45 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.stream.binder.redis.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* Common consumer properties.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.redis.default")
|
||||
class RedisBinderConfigurationProperties {
|
||||
public class ConsumerProperties {
|
||||
|
||||
private int backOffInitialInterval;
|
||||
private int backOffMaxInterval;
|
||||
private double backOffMultiplier;
|
||||
private int concurrency;
|
||||
private int maxAttempts;
|
||||
private int concurrency = 1;
|
||||
|
||||
public int getBackOffInitialInterval() {
|
||||
return backOffInitialInterval;
|
||||
}
|
||||
private boolean partitioned = false;
|
||||
|
||||
public void setBackOffInitialInterval(int backOffInitialInterval) {
|
||||
this.backOffInitialInterval = backOffInitialInterval;
|
||||
}
|
||||
private int instanceCount = 1;
|
||||
|
||||
public int getBackOffMaxInterval() {
|
||||
return backOffMaxInterval;
|
||||
}
|
||||
private int instanceIndex = 0;
|
||||
|
||||
public void setBackOffMaxInterval(int backOffMaxInterval) {
|
||||
this.backOffMaxInterval = backOffMaxInterval;
|
||||
}
|
||||
private int maxAttempts = 3;
|
||||
|
||||
public double getBackOffMultiplier() {
|
||||
return backOffMultiplier;
|
||||
}
|
||||
private int backOffInitialInterval = 1000;
|
||||
|
||||
public void setBackOffMultiplier(double backOffMultiplier) {
|
||||
this.backOffMultiplier = backOffMultiplier;
|
||||
}
|
||||
private int backOffMaxInterval = 10000;
|
||||
|
||||
private double backOffMultiplier = 2.0;
|
||||
|
||||
public int getConcurrency() {
|
||||
return concurrency;
|
||||
@@ -62,11 +47,61 @@ class RedisBinderConfigurationProperties {
|
||||
this.concurrency = concurrency;
|
||||
}
|
||||
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
public boolean isPartitioned() {
|
||||
return partitioned;
|
||||
}
|
||||
|
||||
public void setPartitioned(boolean partitioned) {
|
||||
this.partitioned = partitioned;
|
||||
}
|
||||
|
||||
public int getInstanceCount() {
|
||||
return instanceCount;
|
||||
}
|
||||
|
||||
public void setInstanceCount(int instanceCount) {
|
||||
this.instanceCount = instanceCount;
|
||||
}
|
||||
|
||||
public int getInstanceIndex() {
|
||||
return instanceIndex;
|
||||
}
|
||||
|
||||
public void setInstanceIndex(int instanceIndex) {
|
||||
this.instanceIndex = instanceIndex;
|
||||
}
|
||||
|
||||
public void setMaxAttempts(int maxAttempts) {
|
||||
this.maxAttempts = maxAttempts;
|
||||
}
|
||||
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
}
|
||||
|
||||
public void setBackOffInitialInterval(int backOffInitialInterval) {
|
||||
this.backOffInitialInterval = backOffInitialInterval;
|
||||
}
|
||||
|
||||
public int getBackOffInitialInterval() {
|
||||
return backOffInitialInterval;
|
||||
}
|
||||
|
||||
public void setBackOffMaxInterval(int backOffMaxInterval) {
|
||||
this.backOffMaxInterval = backOffMaxInterval;
|
||||
}
|
||||
|
||||
public int getBackOffMaxInterval() {
|
||||
return backOffMaxInterval;
|
||||
}
|
||||
|
||||
public void setBackOffMultiplier(double backOffMultiplier) {
|
||||
this.backOffMultiplier = backOffMultiplier;
|
||||
}
|
||||
|
||||
public double getBackOffMultiplier() {
|
||||
return backOffMultiplier;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Binder<T> getBinder(String name) {
|
||||
public synchronized Binder<T,?,?> getBinder(String name) {
|
||||
String configurationName;
|
||||
// Fall back to a default if no argument is provided
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
@@ -155,7 +155,7 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
|
||||
ConfigurableApplicationContext binderProducingContext =
|
||||
springApplicationBuilder.run(args.toArray(new String[args.size()]));
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<T> binder = binderProducingContext.getBean(Binder.class);
|
||||
Binder<T,?,?> binder = binderProducingContext.getBean(Binder.class);
|
||||
if (bindersHealthIndicator != null) {
|
||||
OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
|
||||
Map<String, HealthIndicator> indicators = binderProducingContext.getBeansOfType(HealthIndicator.class);
|
||||
@@ -177,16 +177,16 @@ public class DefaultBinderFactory<T> implements BinderFactory<T>, DisposableBean
|
||||
*/
|
||||
private static class BinderInstanceHolder<T> {
|
||||
|
||||
private final Binder<T> binderInstance;
|
||||
private final Binder<T,?,?> binderInstance;
|
||||
|
||||
private final ConfigurableApplicationContext binderContext;
|
||||
|
||||
public BinderInstanceHolder(Binder<T> binderInstance, ConfigurableApplicationContext binderContext) {
|
||||
public BinderInstanceHolder(Binder<T,?,?> binderInstance, ConfigurableApplicationContext binderContext) {
|
||||
this.binderInstance = binderInstance;
|
||||
this.binderContext = binderContext;
|
||||
}
|
||||
|
||||
public Binder<T> getBinderInstance() {
|
||||
public Binder<T,?,?> getBinderInstance() {
|
||||
return this.binderInstance;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,17 +39,13 @@ public class DefaultBinding<T> implements Binding<T> {
|
||||
|
||||
private final AbstractEndpoint endpoint;
|
||||
|
||||
private final DefaultBindingPropertiesAccessor properties;
|
||||
|
||||
public DefaultBinding(String name, String group, T target, AbstractEndpoint endpoint,
|
||||
DefaultBindingPropertiesAccessor properties) {
|
||||
public DefaultBinding(String name, String group, T target, AbstractEndpoint endpoint) {
|
||||
Assert.notNull(target, "target must not be null");
|
||||
Assert.notNull(endpoint, "endpoint must not be null");
|
||||
this.name = name;
|
||||
this.group = group;
|
||||
this.target = target;
|
||||
this.endpoint = endpoint;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +67,6 @@ public class DefaultBinding<T> implements Binding<T> {
|
||||
protected void afterUnbind() {
|
||||
}
|
||||
|
||||
public DefaultBindingPropertiesAccessor getPropertiesAccessor() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return " Binding [name=" + name + ", target=" + target + ", endpoint=" + endpoint.getComponentName()
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for binding-specific property accessors; common properties
|
||||
* are defined here.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class DefaultBindingPropertiesAccessor {
|
||||
|
||||
private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
|
||||
private final Properties properties;
|
||||
|
||||
public DefaultBindingPropertiesAccessor(Properties properties) {
|
||||
if (properties == null) {
|
||||
this.properties = new Properties();
|
||||
}
|
||||
else {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying properties object.
|
||||
* @return The properties.
|
||||
*/
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or null if it doesn't exist.
|
||||
* @param key The property.
|
||||
* @return The key.
|
||||
*/
|
||||
public String getProperty(String key) {
|
||||
return this.properties.getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or the default value if the
|
||||
* property doesn't exist.
|
||||
* @param key The key.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public String getProperty(String key, String defaultValue) {
|
||||
return this.properties.getProperty(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or the default value if the
|
||||
* property doesn't exist.
|
||||
* @param key The key.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public boolean getProperty(String key, boolean defaultValue) {
|
||||
String property = this.properties.getProperty(key);
|
||||
if (property != null) {
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or the default value if the
|
||||
* property doesn't exist.
|
||||
* @param key The key.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public int getProperty(String key, int defaultValue) {
|
||||
String property = this.properties.getProperty(key);
|
||||
if (property != null) {
|
||||
return Integer.parseInt(property);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or the default value if the
|
||||
* property doesn't exist.
|
||||
* @param key The key.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public long getProperty(String key, long defaultValue) {
|
||||
String property = this.properties.getProperty(key);
|
||||
if (property != null) {
|
||||
return Long.parseLong(property);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property for the key, or the default value if the
|
||||
* property doesn't exist.
|
||||
* @param key The key.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public double getProperty(String key, double defaultValue) {
|
||||
String property = properties.getProperty(key);
|
||||
if (property != null) {
|
||||
return Double.parseDouble(property);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'concurrency' property or the default value.
|
||||
* The meaning of concurrency depends on the binder implementation.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public int getConcurrency(int defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.CONCURRENCY, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'maxConcurrency' property or the default value.
|
||||
* The meaning of maxConcurrency depends on the binder implementation.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public int getMaxConcurrency(int defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.MAX_CONCURRENCY, defaultValue);
|
||||
}
|
||||
|
||||
// Retry properties
|
||||
|
||||
/**
|
||||
* Return the 'maxAttempts' property or the default value.
|
||||
* This is used in the retry template's SimpleRetryPolicy
|
||||
* in binders that support retry.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public int getMaxAttempts(int defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.MAX_ATTEMPTS, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'backOffInitialInterval' property or the default value.
|
||||
* This is used in the retry template's ExponentialBackOffPolicy
|
||||
* in binders that support retry.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public long getBackOffInitialInterval(long defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BACK_OFF_INITIAL_INTERVAL, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'backOffMultiplier' property or the default value.
|
||||
* This is used in the retry template's ExponentialBackOffPolicy
|
||||
* in binders that support retry.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public double getBackOffMultiplier(double defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BACK_OFF_MULTIPLIER, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'backOffMaxInterval' property or the default value.
|
||||
* This is used in the retry template's ExponentialBackOffPolicy
|
||||
* in binders that support retry.
|
||||
* @param defaultValue The default value.
|
||||
* @return The property or default value.
|
||||
*/
|
||||
public long getBackOffMaxInterval(long defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BACK_OFF_MAX_INTERVAL, defaultValue);
|
||||
}
|
||||
|
||||
// Partitioning
|
||||
|
||||
/**
|
||||
* A class name for extracting partition keys from messages.
|
||||
* @return The class name,
|
||||
*/
|
||||
public String getPartitionKeyExtractorClass() {
|
||||
return getProperty(BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* The expression to determine the partition key, evaluated against the
|
||||
* message as the root object.
|
||||
* @return The key.
|
||||
*/
|
||||
public Expression getPartitionKeyExpression() {
|
||||
String partionKeyExpression = getProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION);
|
||||
Expression expression = null;
|
||||
if (partionKeyExpression != null) {
|
||||
expression = spelExpressionParser.parseExpression(partionKeyExpression);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class name for calculating a partition from a key.
|
||||
* @return The class name,
|
||||
*/
|
||||
public String getPartitionSelectorClass() {
|
||||
return getProperty(BinderPropertyKeys.PARTITION_SELECTOR_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* The expression evaluated against the partition key to determine
|
||||
* the partition to which the message will be sent. The result should
|
||||
* be an integer that will subsequently be mod'd with the module's
|
||||
* partition count.
|
||||
* @return The expression.
|
||||
*/
|
||||
public Expression getPartitionSelectorExpression() {
|
||||
String partionSelectorExpression = getProperty(BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION);
|
||||
Expression expression = null;
|
||||
if (partionSelectorExpression != null) {
|
||||
expression = spelExpressionParser.parseExpression(partionSelectorExpression);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sequence number for this module.
|
||||
*
|
||||
* @return the sequence number.
|
||||
*/
|
||||
public int getSequence() {
|
||||
return getProperty(BinderPropertyKeys.SEQUENCE, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The module count.
|
||||
*
|
||||
* @return the module count.
|
||||
*/
|
||||
public int getCount() {
|
||||
return getProperty(BinderPropertyKeys.COUNT, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The next module count for non-sink modules
|
||||
* @return the next module count
|
||||
*/
|
||||
public int getNextModuleCount() {
|
||||
return getProperty(BinderPropertyKeys.NEXT_MODULE_COUNT, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The partition index that this consumer supports.
|
||||
* @return The partition index.
|
||||
*/
|
||||
public int getPartitionIndex() {
|
||||
return getProperty(BinderPropertyKeys.PARTITION_INDEX, -1);
|
||||
}
|
||||
|
||||
// Batching
|
||||
|
||||
/**
|
||||
* If true, enable batching.
|
||||
* @param defaultValue the default value.
|
||||
* @return the property or default value.
|
||||
*/
|
||||
public boolean isBatchingEnabled(boolean defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BATCHING_ENABLED, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The batch size.
|
||||
* @param defaultValue the default value.
|
||||
* @return the property or default value.
|
||||
*/
|
||||
public int getBatchSize(int defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BATCH_SIZE, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The batch buffer limit.
|
||||
* @param defaultValue the default value.
|
||||
* @return the property or default value.
|
||||
*/
|
||||
public int geteBatchBufferLimit(int defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BATCH_BUFFER_LIMIT, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The batch timeout.
|
||||
* @param defaultValue the default value.
|
||||
* @return the property or default value.
|
||||
*/
|
||||
public long getBatchTimeout(long defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.BATCH_TIMEOUT, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, messages will be compressed.
|
||||
* @param defaultValue the default value.
|
||||
* @return the property or default value.
|
||||
*/
|
||||
public boolean isCompress(boolean defaultValue) {
|
||||
return getProperty(BinderPropertyKeys.COMPRESS, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of groups for which the binder will ensure message delivery, even if their consumers bind
|
||||
* after the producer. This is a producer-property only.
|
||||
* @param defaultValue the default value
|
||||
* @return the property, parsed as a comma-separated list of values
|
||||
*/
|
||||
public String[] getRequiredGroups(String[] defaultValue) {
|
||||
String requiredGroupsValue = getProperty(BinderPropertyKeys.REQUIRED_GROUPS, "");
|
||||
return StringUtils.commaDelimitedListToStringArray(requiredGroupsValue);
|
||||
}
|
||||
|
||||
|
||||
// Utility methods
|
||||
|
||||
/**
|
||||
* Convert a comma-delimited String property to a String[] if
|
||||
* present, or return the default value.
|
||||
* @param value The property value.
|
||||
* @param defaultValue The default value.
|
||||
* @return The converted property or default value.
|
||||
*/
|
||||
protected String[] asStringArray(String value, String[] defaultValue) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return StringUtils.commaDelimitedListToStringArray(value);
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.properties.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,11 +18,9 @@ package org.springframework.cloud.stream.binder;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class to determine if a binding is configured for partitioning
|
||||
@@ -44,7 +42,7 @@ public class PartitionHandler {
|
||||
|
||||
private final PartitionSelectorStrategy partitionSelector;
|
||||
|
||||
private final PartitioningMetadata metadata;
|
||||
private final ProducerProperties producerProperties;
|
||||
|
||||
|
||||
/**
|
||||
@@ -54,29 +52,18 @@ public class PartitionHandler {
|
||||
* @param evaluationContext evaluation context for binder
|
||||
* @param partitionSelector configured partition selector; may be {@code null}
|
||||
* @param properties binder properties
|
||||
* @param partitionCount number of partitions configured for binder
|
||||
*/
|
||||
public PartitionHandler(ConfigurableListableBeanFactory beanFactory,
|
||||
EvaluationContext evaluationContext,
|
||||
PartitionSelectorStrategy partitionSelector,
|
||||
DefaultBindingPropertiesAccessor properties, int partitionCount) {
|
||||
EvaluationContext evaluationContext,
|
||||
PartitionSelectorStrategy partitionSelector,
|
||||
ProducerProperties properties) {
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
this.beanFactory = beanFactory;
|
||||
this.evaluationContext = evaluationContext;
|
||||
this.partitionSelector = partitionSelector == null
|
||||
? new DefaultPartitionSelector()
|
||||
: partitionSelector;
|
||||
this.metadata = new PartitioningMetadata(properties, partitionCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the binder properties provided indicate
|
||||
* that this binder is configured for partitioning.
|
||||
*
|
||||
* @return true if partitioning is enabled
|
||||
*/
|
||||
public boolean isPartitionedModule() {
|
||||
return this.metadata.isPartitionedModule();
|
||||
this.producerProperties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,27 +88,27 @@ public class PartitionHandler {
|
||||
Object key = extractKey(message);
|
||||
|
||||
int partition;
|
||||
if (this.metadata.hasSelectorClass()) {
|
||||
if (this.producerProperties.getPartitionSelectorClass() != null) {
|
||||
partition = invokePartitionSelector(key);
|
||||
}
|
||||
else if (this.metadata.hasSelectorExpression()) {
|
||||
partition = this.metadata.partitionSelectorExpression.getValue(
|
||||
else if (this.producerProperties.getPartitionSelectorExpression() != null) {
|
||||
partition = this.producerProperties.getPartitionSelectorExpression().getValue(
|
||||
this.evaluationContext, key, Integer.class);
|
||||
}
|
||||
else {
|
||||
partition = this.partitionSelector.selectPartition(key, metadata.partitionCount);
|
||||
partition = this.partitionSelector.selectPartition(key, producerProperties.getPartitionCount());
|
||||
}
|
||||
// protection in case a user selector returns a negative.
|
||||
return Math.abs(partition % metadata.partitionCount);
|
||||
return Math.abs(partition % producerProperties.getPartitionCount());
|
||||
}
|
||||
|
||||
private Object extractKey(Message<?> message) {
|
||||
Object key = null;
|
||||
if (this.metadata.hasKeyExtractorClass()) {
|
||||
if (this.producerProperties.getPartitionKeyExtractorClass() != null) {
|
||||
key = invokeKeyExtractor(message);
|
||||
}
|
||||
else if (this.metadata.hasKeyExpression()) {
|
||||
key = this.metadata.partitionKeyExpression.getValue(this.evaluationContext, message);
|
||||
else if (this.producerProperties.getPartitionKeyExpression() != null) {
|
||||
key = this.producerProperties.getPartitionKeyExpression().getValue(this.evaluationContext, message);
|
||||
}
|
||||
Assert.notNull(key, "Partition key cannot be null");
|
||||
|
||||
@@ -130,16 +117,16 @@ public class PartitionHandler {
|
||||
|
||||
private Object invokeKeyExtractor(Message<?> message) {
|
||||
PartitionKeyExtractorStrategy strategy = getBean(
|
||||
metadata.partitionKeyExtractorClass,
|
||||
producerProperties.getPartitionKeyExtractorClass().getName(),
|
||||
PartitionKeyExtractorStrategy.class);
|
||||
return strategy.extractKey(message);
|
||||
}
|
||||
|
||||
private int invokePartitionSelector(Object key) {
|
||||
PartitionSelectorStrategy strategy = getBean(
|
||||
metadata.partitionSelectorClass,
|
||||
producerProperties.getPartitionSelectorClass().getName(),
|
||||
PartitionSelectorStrategy.class);
|
||||
return strategy.selectPartition(key, metadata.partitionCount);
|
||||
return strategy.selectPartition(key, producerProperties.getPartitionCount());
|
||||
}
|
||||
|
||||
private <T> T getBean(String className, Class<T> type) {
|
||||
@@ -190,46 +177,4 @@ public class PartitionHandler {
|
||||
|
||||
}
|
||||
|
||||
private static class PartitioningMetadata {
|
||||
|
||||
private final String partitionKeyExtractorClass;
|
||||
|
||||
private final Expression partitionKeyExpression;
|
||||
|
||||
private final String partitionSelectorClass;
|
||||
|
||||
private final Expression partitionSelectorExpression;
|
||||
|
||||
private final int partitionCount;
|
||||
|
||||
public PartitioningMetadata(DefaultBindingPropertiesAccessor properties, int partitionCount) {
|
||||
this.partitionCount = partitionCount;
|
||||
this.partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass();
|
||||
this.partitionKeyExpression = properties.getPartitionKeyExpression();
|
||||
this.partitionSelectorClass = properties.getPartitionSelectorClass();
|
||||
this.partitionSelectorExpression = properties.getPartitionSelectorExpression();
|
||||
}
|
||||
|
||||
public boolean isPartitionedModule() {
|
||||
return StringUtils.hasText(this.partitionKeyExtractorClass) || this.partitionKeyExpression != null;
|
||||
}
|
||||
|
||||
public boolean hasSelectorClass() {
|
||||
return StringUtils.hasText(this.partitionSelectorClass);
|
||||
}
|
||||
|
||||
public boolean hasKeyExtractorClass() {
|
||||
return StringUtils.hasText(this.partitionKeyExtractorClass);
|
||||
}
|
||||
|
||||
public boolean hasSelectorExpression() {
|
||||
return partitionSelectorExpression != null;
|
||||
}
|
||||
|
||||
public boolean hasKeyExpression() {
|
||||
return partitionKeyExpression != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
|
||||
/**
|
||||
* Common producer properties.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class ProducerProperties {
|
||||
|
||||
private Expression partitionKeyExpression = null;
|
||||
|
||||
private Class<?> partitionKeyExtractorClass = null;
|
||||
|
||||
private Class<?> partitionSelectorClass = null;
|
||||
|
||||
private Expression partitionSelectorExpression = null;
|
||||
|
||||
private int partitionCount = 1;
|
||||
|
||||
private String[] requiredGroups = new String[] {};
|
||||
|
||||
public Expression getPartitionKeyExpression() {
|
||||
return partitionKeyExpression;
|
||||
}
|
||||
|
||||
public void setPartitionKeyExpression(Expression partitionKeyExpression) {
|
||||
this.partitionKeyExpression = partitionKeyExpression;
|
||||
}
|
||||
|
||||
public Class<?> getPartitionKeyExtractorClass() {
|
||||
return partitionKeyExtractorClass;
|
||||
}
|
||||
|
||||
public void setPartitionKeyExtractorClass(Class<?> partitionKeyExtractorClass) {
|
||||
this.partitionKeyExtractorClass = partitionKeyExtractorClass;
|
||||
}
|
||||
|
||||
public boolean isPartitioned() {
|
||||
return this.partitionKeyExpression != null || partitionKeyExtractorClass != null;
|
||||
}
|
||||
|
||||
public Class<?> getPartitionSelectorClass() {
|
||||
return partitionSelectorClass;
|
||||
}
|
||||
|
||||
public void setPartitionSelectorClass(Class<?> partitionSelectorClass) {
|
||||
this.partitionSelectorClass = partitionSelectorClass;
|
||||
}
|
||||
|
||||
public Expression getPartitionSelectorExpression() {
|
||||
return partitionSelectorExpression;
|
||||
}
|
||||
|
||||
public void setPartitionSelectorExpression(Expression partitionSelectorExpression) {
|
||||
this.partitionSelectorExpression = partitionSelectorExpression;
|
||||
}
|
||||
|
||||
public int getPartitionCount() {
|
||||
return partitionCount;
|
||||
}
|
||||
|
||||
public void setPartitionCount(int partitionCount) {
|
||||
this.partitionCount = partitionCount;
|
||||
}
|
||||
|
||||
public String[] getRequiredGroups() {
|
||||
return requiredGroups;
|
||||
}
|
||||
|
||||
public void setRequiredGroups(String... requiredGroups) {
|
||||
this.requiredGroups = requiredGroups;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.stream.binding;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -35,7 +34,6 @@ import org.springframework.util.ObjectUtils;
|
||||
* resolves the channel from the bean factory and, if not present, creates a new channel
|
||||
* and adds it to the factory after binding it to the binder. The binder is optionally
|
||||
* determined with a prefix preceding a colon.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
@@ -50,9 +48,11 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
public BinderAwareChannelResolver(BinderFactory binderFactory,
|
||||
ChannelBindingServiceProperties channelBindingServiceProperties, DynamicDestinationsBindable dynamicDestinationsBindable) {
|
||||
public BinderAwareChannelResolver(BinderFactory binderFactory, ChannelBindingServiceProperties channelBindingServiceProperties,
|
||||
DynamicDestinationsBindable dynamicDestinationsBindable) {
|
||||
Assert.notNull(binderFactory, "'binderFactory' cannot be null");
|
||||
Assert.notNull(channelBindingServiceProperties, "'channelBindingServiceProperties' cannot be null");
|
||||
Assert.notNull(dynamicDestinationsBindable, "'dynamicDestinationBindable' cannot be null");
|
||||
this.binderFactory = binderFactory;
|
||||
this.channelBindingServiceProperties = channelBindingServiceProperties;
|
||||
this.dynamicDestinationsBindable = dynamicDestinationsBindable;
|
||||
@@ -67,11 +67,11 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel resolveDestination(String destinationName) {
|
||||
public MessageChannel resolveDestination(String channelName) {
|
||||
MessageChannel channel = null;
|
||||
DestinationResolutionException destinationResolutionException;
|
||||
try {
|
||||
return super.resolveDestination(destinationName);
|
||||
return super.resolveDestination(channelName);
|
||||
}
|
||||
catch (DestinationResolutionException e) {
|
||||
destinationResolutionException = e;
|
||||
@@ -79,33 +79,38 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
|
||||
synchronized (this) {
|
||||
if (this.beanFactory != null && this.binderFactory != null) {
|
||||
String[] dynamicDestinations = null;
|
||||
Properties producerProperties = null;
|
||||
if (this.channelBindingServiceProperties != null) {
|
||||
dynamicDestinations = this.channelBindingServiceProperties.getDynamicDestinations();
|
||||
// TODO: need the props to return some defaults if not found
|
||||
producerProperties = this.channelBindingServiceProperties.getProducerProperties(destinationName);
|
||||
}
|
||||
boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations)
|
||||
|| ObjectUtils.containsElement(dynamicDestinations, destinationName);
|
||||
|| ObjectUtils.containsElement(dynamicDestinations, channelName);
|
||||
if (dynamicAllowed) {
|
||||
String transport = null;
|
||||
String beanName = destinationName;
|
||||
if (destinationName.contains(":")) {
|
||||
String[] tokens = destinationName.split(":", 2);
|
||||
String binderName = null;
|
||||
String beanName = channelName;
|
||||
if (channelName.contains(":")) {
|
||||
String[] tokens = channelName.split(":", 2);
|
||||
if (tokens.length == 2) {
|
||||
transport = tokens[0];
|
||||
destinationName = tokens[1];
|
||||
binderName = tokens[0];
|
||||
channelName = tokens[1];
|
||||
}
|
||||
else if (tokens.length != 1) {
|
||||
throw new IllegalArgumentException("Unrecognized channel naming scheme: " + destinationName + " , should be" +
|
||||
" [<transport>:]<destinationName>");
|
||||
throw new IllegalArgumentException("Unrecognized channel naming scheme: " + channelName + " , should be" +
|
||||
" [<binder>:]<channelName>");
|
||||
}
|
||||
}
|
||||
channel = new DirectChannel();
|
||||
this.beanFactory.registerSingleton(beanName, channel);
|
||||
channel = (MessageChannel) this.beanFactory.initializeBean(channel, beanName);
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder(transport);
|
||||
this.dynamicDestinationsBindable.addOutputBinding(beanName, binder.bindProducer(destinationName, channel, producerProperties));
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel, ?, ProducerProperties> binder =
|
||||
(Binder<MessageChannel, ?, ProducerProperties>) binderFactory.getBinder(binderName);
|
||||
Class<? extends ProducerProperties> producerPropertiesClass =
|
||||
ChannelBindingService.resolveProducerPropertiesType(binder);
|
||||
ProducerProperties producerProperties =
|
||||
this.channelBindingServiceProperties.getProducerProperties(channelName, producerPropertiesClass);
|
||||
String destinationName = this.channelBindingServiceProperties.getBindingDestination(channelName);
|
||||
this.dynamicDestinationsBindable.addOutputBinding(beanName,
|
||||
binder.bindProducer(destinationName, channel, producerProperties));
|
||||
}
|
||||
else {
|
||||
throw destinationResolutionException;
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -29,8 +28,10 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -63,29 +64,34 @@ public class ChannelBindingService {
|
||||
this.binderFactory = binderFactory;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<Binding<MessageChannel>> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName);
|
||||
String[] channelBindingTargets = StringUtils.commaDelimitedListToStringArray(channelBindingTarget);
|
||||
List<Binding<MessageChannel>> bindings = new ArrayList<>();
|
||||
|
||||
Binder<MessageChannel> binder = getBinderForChannel(inputChannelName);
|
||||
String consumerGroup = consumerGroup(inputChannelName);
|
||||
Properties consumerProperties = this.channelBindingServiceProperties.getConsumerProperties(inputChannelName);
|
||||
|
||||
Binder<MessageChannel, ConsumerProperties, ?> binder =
|
||||
(Binder<MessageChannel, ConsumerProperties, ?>) getBinderForChannel(inputChannelName);
|
||||
Class<? extends ConsumerProperties> propertiesClass = resolveConsumerPropertiesType(binder);
|
||||
ConsumerProperties consumerProperties =
|
||||
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName, propertiesClass);
|
||||
for (String target : channelBindingTargets) {
|
||||
Binding<MessageChannel> binding = binder.bindConsumer(target, consumerGroup, inputChannel,
|
||||
consumerProperties);
|
||||
Binding<MessageChannel> binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName),
|
||||
inputChannel, consumerProperties);
|
||||
bindings.add(binding);
|
||||
}
|
||||
this.consumerBindings.put(inputChannelName, bindings);
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Binding<MessageChannel> bindProducer(MessageChannel outputChannel, String outputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName);
|
||||
Binder<MessageChannel> binder = getBinderForChannel(outputChannelName);
|
||||
Binding<MessageChannel> binding = binder.bindProducer(channelBindingTarget, outputChannel,
|
||||
this.channelBindingServiceProperties.getProducerProperties(outputChannelName));
|
||||
Binder<MessageChannel, ?, ProducerProperties> binder =
|
||||
(Binder<MessageChannel, ?, ProducerProperties>) getBinderForChannel(outputChannelName);
|
||||
Class<? extends ProducerProperties> propertiesClass = resolveProducerPropertiesType(binder);
|
||||
ProducerProperties producerProperties =
|
||||
this.channelBindingServiceProperties.getProducerProperties(outputChannelName, propertiesClass);
|
||||
Binding<MessageChannel> binding = binder.bindProducer(channelBindingTarget, outputChannel, producerProperties);
|
||||
this.producerBindings.put(outputChannelName, binding);
|
||||
return binding;
|
||||
}
|
||||
@@ -112,15 +118,49 @@ public class ChannelBindingService {
|
||||
}
|
||||
}
|
||||
|
||||
private Binder<MessageChannel> getBinderForChannel(String channelName) {
|
||||
private Binder<MessageChannel, ?, ?> getBinderForChannel(String channelName) {
|
||||
String transport = this.channelBindingServiceProperties.getBinder(channelName);
|
||||
return binderFactory.getBinder(transport);
|
||||
}
|
||||
|
||||
private String consumerGroup(String inputChannelName) {
|
||||
BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindings()
|
||||
.get(inputChannelName);
|
||||
return bindingProperties == null ? null : bindingProperties.getGroup();
|
||||
|
||||
static Class<? extends ConsumerProperties> resolveConsumerPropertiesType(Binder<?, ?, ?> binder) {
|
||||
return resolveTypeForBinder(binder, ConsumerProperties.class);
|
||||
}
|
||||
|
||||
static Class<? extends ProducerProperties> resolveProducerPropertiesType(Binder<?, ?, ?> binder) {
|
||||
return resolveTypeForBinder(binder, ProducerProperties.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Class<? extends T> resolveTypeForBinder(Binder<?, ?, ?> binder, Class<? extends T> upperBound) {
|
||||
Class<? extends T> propertiesClass = null;
|
||||
ResolvableType currentType = ResolvableType.forType(binder.getClass());
|
||||
while (!Object.class.equals(currentType.getRawClass()) && propertiesClass == null) {
|
||||
ResolvableType[] interfaces = currentType.getInterfaces();
|
||||
ResolvableType binderResolvableType = null;
|
||||
for (ResolvableType interfaceType : interfaces) {
|
||||
if (Binder.class.equals(interfaceType.getRawClass())) {
|
||||
binderResolvableType = interfaceType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (binderResolvableType == null) {
|
||||
currentType = currentType.getSuperType();
|
||||
}
|
||||
else {
|
||||
ResolvableType[] generics = binderResolvableType.getGenerics();
|
||||
for (ResolvableType generic : generics) {
|
||||
Class<?> resolvedParameter = generic.resolve();
|
||||
if (resolvedParameter != null && upperBound.isAssignableFrom(resolvedParameter)) {
|
||||
propertiesClass = (Class<? extends T>) resolvedParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propertiesClass == null) {
|
||||
propertiesClass = upperBound;
|
||||
}
|
||||
return propertiesClass;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
|
||||
public void configureMessageChannel(MessageChannel channel, String channelName) {
|
||||
Assert.isAssignable(AbstractMessageChannel.class, channel.getClass());
|
||||
AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel;
|
||||
BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindings().get(channelName);
|
||||
BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindingProperties(channelName);
|
||||
if (bindingProperties != null) {
|
||||
String contentType = bindingProperties.getContentType();
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
|
||||
@@ -52,7 +52,7 @@ public class MessageHistoryTrackerConfigurer implements MessageChannelConfigurer
|
||||
|
||||
@Override
|
||||
public void configureMessageChannel(MessageChannel messageChannel, String channelName) {
|
||||
BindingProperties bindingProperties = channelBindingServiceProperties.getBindings().get(channelName);
|
||||
BindingProperties bindingProperties = channelBindingServiceProperties.getBindingProperties(channelName);
|
||||
if (bindingProperties != null && Boolean.TRUE.equals(bindingProperties.isTrackHistory())) {
|
||||
final Set<String> trackHistoryProperties = StringUtils.commaDelimitedListToSet(bindingProperties.getTrackedProperties());
|
||||
Map<String, Object> channelBindingServicePropertiesMap = channelBindingServiceProperties.asMapProperties();
|
||||
|
||||
@@ -16,14 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.stream.config;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
/**
|
||||
* Contains the properties of a binding.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
@@ -41,9 +38,8 @@ public class BindingProperties {
|
||||
/**
|
||||
* Unique name that the binding belongs to (applies to consumers only). Multiple consumers within the same group
|
||||
* share the subscription. A null or empty String value indicates an anonymous group that is not shared.
|
||||
*
|
||||
* @see org.springframework.cloud.stream.binder.Binder#bindConsumer(java.lang.String, java.lang.String,
|
||||
* java.lang.Object, java.util.Properties)
|
||||
* java.lang.Object, org.springframework.cloud.stream.binder.ConsumerProperties)
|
||||
*/
|
||||
private String group;
|
||||
|
||||
@@ -65,44 +61,6 @@ public class BindingProperties {
|
||||
*/
|
||||
private String trackedProperties = "all";
|
||||
|
||||
// Outbound properties
|
||||
|
||||
private String requiredGroups;
|
||||
|
||||
// Partition properties
|
||||
|
||||
private String partitionKeyExpression;
|
||||
|
||||
private String partitionKeyExtractorClass;
|
||||
|
||||
private String partitionSelectorClass;
|
||||
|
||||
private String partitionSelectorExpression;
|
||||
|
||||
private Integer partitionCount = 1;
|
||||
|
||||
private Integer nextModuleCount;
|
||||
|
||||
private Integer nextModuleConcurrency;
|
||||
|
||||
// Batching properties
|
||||
private Boolean batchingEnabled;
|
||||
|
||||
private Integer batchSize;
|
||||
|
||||
private Integer batchBufferLimit;
|
||||
|
||||
private Integer batchTimeout;
|
||||
|
||||
// Inbound properties
|
||||
private Integer concurrency;
|
||||
|
||||
// Partition properties
|
||||
private String partitionIndex;
|
||||
|
||||
private Boolean partitioned = false;
|
||||
|
||||
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
@@ -143,126 +101,6 @@ public class BindingProperties {
|
||||
this.trackHistory = trackHistory;
|
||||
}
|
||||
|
||||
public String getPartitionKeyExpression() {
|
||||
return this.partitionKeyExpression;
|
||||
}
|
||||
|
||||
public void setPartitionKeyExpression(String partitionKeyExpression) {
|
||||
this.partitionKeyExpression = partitionKeyExpression;
|
||||
}
|
||||
|
||||
public String getPartitionKeyExtractorClass() {
|
||||
return this.partitionKeyExtractorClass;
|
||||
}
|
||||
|
||||
public void setPartitionKeyExtractorClass(String partitionKeyExtractorClass) {
|
||||
this.partitionKeyExtractorClass = partitionKeyExtractorClass;
|
||||
}
|
||||
|
||||
public String getPartitionSelectorClass() {
|
||||
return this.partitionSelectorClass;
|
||||
}
|
||||
|
||||
public void setPartitionSelectorClass(String partitionSelectorClass) {
|
||||
this.partitionSelectorClass = partitionSelectorClass;
|
||||
}
|
||||
|
||||
public String getPartitionSelectorExpression() {
|
||||
return this.partitionSelectorExpression;
|
||||
}
|
||||
|
||||
public void setPartitionSelectorExpression(String partitionSelectorExpression) {
|
||||
this.partitionSelectorExpression = partitionSelectorExpression;
|
||||
}
|
||||
|
||||
public Integer getNextModuleCount() {
|
||||
return this.nextModuleCount;
|
||||
}
|
||||
|
||||
public void setNextModuleCount(Integer nextModuleCount) {
|
||||
this.nextModuleCount = nextModuleCount;
|
||||
}
|
||||
|
||||
public Integer getNextModuleConcurrency() {
|
||||
return this.nextModuleConcurrency;
|
||||
}
|
||||
|
||||
public void setNextModuleConcurrency(Integer nextModuleConcurrency) {
|
||||
this.nextModuleConcurrency = nextModuleConcurrency;
|
||||
}
|
||||
|
||||
public Boolean isBatchingEnabled() {
|
||||
return this.batchingEnabled;
|
||||
}
|
||||
|
||||
public void setBatchingEnabled(Boolean batchingEnabled) {
|
||||
this.batchingEnabled = batchingEnabled;
|
||||
}
|
||||
|
||||
public Integer getBatchSize() {
|
||||
return this.batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(Integer batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public Integer getBatchBufferLimit() {
|
||||
return this.batchBufferLimit;
|
||||
}
|
||||
|
||||
public void setBatchBufferLimit(Integer batchBufferLimit) {
|
||||
this.batchBufferLimit = batchBufferLimit;
|
||||
}
|
||||
|
||||
public Integer getBatchTimeout() {
|
||||
return this.batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(Integer batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
|
||||
public Integer getPartitionCount() {
|
||||
return this.partitionCount;
|
||||
}
|
||||
|
||||
public void setPartitionCount(Integer partitionCount) {
|
||||
this.partitionCount = partitionCount;
|
||||
}
|
||||
|
||||
public Integer getConcurrency() {
|
||||
return this.concurrency;
|
||||
}
|
||||
|
||||
public void setConcurrency(Integer concurrency) {
|
||||
this.concurrency = concurrency;
|
||||
}
|
||||
|
||||
public String getPartitionIndex() {
|
||||
return this.partitionIndex;
|
||||
}
|
||||
|
||||
public void setPartitionIndex(String partitionIndex) {
|
||||
this.partitionIndex = partitionIndex;
|
||||
}
|
||||
|
||||
public Boolean isPartitioned() {
|
||||
return this.partitioned;
|
||||
}
|
||||
|
||||
public void setPartitioned(Boolean partitioned) {
|
||||
this.partitioned = partitioned;
|
||||
}
|
||||
|
||||
public String getRequiredGroups() {
|
||||
return requiredGroups;
|
||||
}
|
||||
|
||||
public void setRequiredGroups(String requiredGroups) {
|
||||
this.requiredGroups = requiredGroups;
|
||||
}
|
||||
|
||||
public String getTrackedProperties() {
|
||||
return this.trackedProperties;
|
||||
}
|
||||
@@ -289,66 +127,6 @@ public class BindingProperties {
|
||||
sb.append("trackHistory=" + this.trackHistory);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionKeyExpression != null && !this.partitionKeyExpression.isEmpty()) {
|
||||
sb.append("partitionKeyExpression=" + partitionKeyExpression);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionKeyExtractorClass != null && !this.partitionKeyExtractorClass.isEmpty()) {
|
||||
sb.append("partitionKeyExtractorClass=" + partitionKeyExtractorClass);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionSelectorClass != null && !this.partitionSelectorClass.isEmpty()) {
|
||||
sb.append("partitionSelectorClass=" + partitionSelectorClass);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionSelectorExpression != null && !this.partitionSelectorExpression.isEmpty()) {
|
||||
sb.append("partitionSelectorExpression=" + partitionSelectorExpression);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionCount != null) {
|
||||
sb.append("partitionCount=" + this.partitionCount);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.nextModuleCount != null) {
|
||||
sb.append("nextModuleCount=" + this.nextModuleCount);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.nextModuleConcurrency != null) {
|
||||
sb.append("nextModuleConcurrency=" + this.nextModuleConcurrency);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.batchingEnabled != null) {
|
||||
sb.append("batchingEnabled=" + this.batchingEnabled);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.batchSize != null) {
|
||||
sb.append("batchSize=" + this.batchSize);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.batchBufferLimit != null) {
|
||||
sb.append("batchBufferLimit=" + this.batchBufferLimit);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.batchTimeout != null) {
|
||||
sb.append("batchTimeout=" + this.batchTimeout);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitioned != null) {
|
||||
sb.append("partitioned=" + this.partitioned);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.partitionIndex != null) {
|
||||
sb.append("partitionIndex=" + this.partitionIndex);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (this.concurrency != null) {
|
||||
sb.append("concurrency=" + this.concurrency);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
if (!StringUtils.isEmpty(requiredGroups)) {
|
||||
sb.append("requiredGroups=" + requiredGroups);
|
||||
sb.append(COMMA);
|
||||
}
|
||||
sb.deleteCharAt(sb.lastIndexOf(COMMA));
|
||||
return "BindingProperties{" + sb.toString() + "}";
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.config;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* Converter that transforms {@link String} expressions into {@link BindingProperties}. Useful for shorthand
|
||||
* binding property configuration.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class BindingPropertiesConverter implements Converter<String,BindingProperties> {
|
||||
|
||||
public BindingPropertiesConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingProperties convert(String bindingConfiguration) {
|
||||
BindingProperties bindingProperties = new BindingProperties();
|
||||
// for now, just configure the destination - in the future do some more advanced parsing
|
||||
bindingProperties.setDestination(bindingConfiguration);
|
||||
return bindingProperties;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binding.BindableChannelFactory;
|
||||
@@ -49,7 +48,6 @@ import org.springframework.cloud.stream.binding.SingleChannelBindable;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
|
||||
@@ -140,11 +138,6 @@ public class ChannelBindingServiceConfiguration {
|
||||
return new BinderAwareChannelResolver(binderFactory, channelBindingServiceProperties, dynamicBindable());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
public Converter<String, BindingProperties> bindingPropertiesConverter() {
|
||||
return new BindingPropertiesConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty("spring.cloud.stream.bindings." + ERROR_CHANNEL_NAME + ".destination")
|
||||
|
||||
@@ -16,19 +16,36 @@
|
||||
|
||||
package org.springframework.cloud.stream.config;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.bind.PropertySourcesPropertyValues;
|
||||
import org.springframework.boot.bind.RelaxedDataBinder;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Marius Bogoevici
|
||||
@@ -37,7 +54,20 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.stream")
|
||||
@JsonInclude(Include.NON_DEFAULT)
|
||||
public class ChannelBindingServiceProperties {
|
||||
public class ChannelBindingServiceProperties implements ApplicationContextAware, InitializingBean {
|
||||
|
||||
private final static String[] bindingPropertyFields;
|
||||
|
||||
static {
|
||||
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(BindingProperties.class);
|
||||
List<String> propertyNames = new ArrayList<>();
|
||||
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
||||
propertyNames.add(propertyDescriptor.getName());
|
||||
}
|
||||
bindingPropertyFields = propertyNames.toArray(new String[propertyNames.size()]);
|
||||
}
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}")
|
||||
private int instanceIndex = 0;
|
||||
@@ -48,10 +78,18 @@ public class ChannelBindingServiceProperties {
|
||||
|
||||
private Map<String, BinderProperties> binders = new HashMap<>();
|
||||
|
||||
private Properties consumerDefaults = new Properties();
|
||||
|
||||
private Properties producerDefaults = new Properties();
|
||||
|
||||
private String defaultBinder;
|
||||
|
||||
private String[] dynamicDestinations = new String[0];
|
||||
|
||||
private boolean ignoreUnknownProperties = true;
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
public Map<String, BindingProperties> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
@@ -100,128 +138,44 @@ public class ChannelBindingServiceProperties {
|
||||
this.dynamicDestinations = dynamicDestinations;
|
||||
}
|
||||
|
||||
public String getBindingDestination(String channelName) {
|
||||
BindingProperties bindingProperties = bindings.get(channelName);
|
||||
// we may shortcut directly to the path
|
||||
// just return the channel name if not found
|
||||
return bindingProperties != null && StringUtils.hasText(bindingProperties.getDestination()) ?
|
||||
bindingProperties.getDestination() : channelName;
|
||||
public Properties getConsumerDefaults() {
|
||||
return consumerDefaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get consumer properties for the given input channel name.
|
||||
*
|
||||
* @param inputChannelName the input channel name
|
||||
* @return merged consumer properties
|
||||
*/
|
||||
public Properties getConsumerProperties(String inputChannelName) {
|
||||
Properties channelConsumerProperties = new Properties();
|
||||
BindingProperties bindingProperties = this.bindings.get(inputChannelName);
|
||||
if (bindingProperties != null) {
|
||||
if (bindingProperties.getConcurrency() != null) {
|
||||
channelConsumerProperties.setProperty(BinderPropertyKeys.CONCURRENCY,
|
||||
Integer.toString(bindingProperties.getConcurrency()));
|
||||
}
|
||||
updateConsumerPartitionProperties(inputChannelName, channelConsumerProperties);
|
||||
}
|
||||
return channelConsumerProperties;
|
||||
public void setConsumerDefaults(Properties consumerDefaults) {
|
||||
this.consumerDefaults = consumerDefaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get producer properties for the given output channel name.
|
||||
*
|
||||
* @param outputChannelName the output channel name
|
||||
* @return merged producer properties
|
||||
*/
|
||||
public Properties getProducerProperties(String outputChannelName) {
|
||||
Properties channelProducerProperties = new Properties();
|
||||
BindingProperties bindingProperties = this.bindings.get(outputChannelName);
|
||||
if (bindingProperties != null) {
|
||||
updateBatchProperties(bindingProperties, channelProducerProperties);
|
||||
updateProducerPartitionProperties(bindingProperties, channelProducerProperties);
|
||||
if (StringUtils.hasText(bindingProperties.getRequiredGroups())) {
|
||||
channelProducerProperties.setProperty(BinderPropertyKeys.REQUIRED_GROUPS,
|
||||
bindingProperties.getRequiredGroups());
|
||||
}
|
||||
}
|
||||
return channelProducerProperties;
|
||||
public Properties getProducerDefaults() {
|
||||
return producerDefaults;
|
||||
}
|
||||
|
||||
private boolean isPartitionedConsumer(String channelName) {
|
||||
BindingProperties bindingProperties = bindings.get(channelName);
|
||||
return bindingProperties != null && bindingProperties.isPartitioned();
|
||||
public void setProducerDefaults(Properties producerDefaults) {
|
||||
this.producerDefaults = producerDefaults;
|
||||
}
|
||||
|
||||
private boolean isPartitionedProducer(BindingProperties bindingProperties) {
|
||||
return (StringUtils.hasText(bindingProperties.getPartitionKeyExpression())
|
||||
|| StringUtils.hasText(bindingProperties.getPartitionKeyExtractorClass()));
|
||||
public boolean isIgnoreUnknownProperties() {
|
||||
return ignoreUnknownProperties;
|
||||
}
|
||||
|
||||
private void updateBatchProperties(BindingProperties bindingProperties, Properties producerProperties) {
|
||||
if (bindingProperties.isBatchingEnabled() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.BATCHING_ENABLED,
|
||||
String.valueOf(bindingProperties.isBatchingEnabled()));
|
||||
}
|
||||
if (bindingProperties.getBatchSize() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.BATCH_SIZE,
|
||||
String.valueOf(bindingProperties.getBatchSize()));
|
||||
}
|
||||
if (bindingProperties.getBatchBufferLimit() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.BATCH_BUFFER_LIMIT,
|
||||
String.valueOf(bindingProperties.getBatchBufferLimit()));
|
||||
}
|
||||
if (bindingProperties.getBatchTimeout() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.BATCH_TIMEOUT,
|
||||
String.valueOf(bindingProperties.getBatchTimeout()));
|
||||
}
|
||||
public void setIgnoreUnknownProperties(boolean ignoreUnknownProperties) {
|
||||
this.ignoreUnknownProperties = ignoreUnknownProperties;
|
||||
}
|
||||
|
||||
private void updateProducerPartitionProperties(BindingProperties bindingProperties, Properties producerProperties) {
|
||||
if (isPartitionedProducer(bindingProperties)) {
|
||||
if (bindingProperties.getPartitionKeyExpression() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION,
|
||||
bindingProperties.getPartitionKeyExpression());
|
||||
}
|
||||
if (bindingProperties.getPartitionKeyExtractorClass() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS,
|
||||
bindingProperties.getPartitionKeyExtractorClass());
|
||||
}
|
||||
if (bindingProperties.getPartitionSelectorClass() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.PARTITION_SELECTOR_CLASS,
|
||||
bindingProperties.getPartitionSelectorClass());
|
||||
}
|
||||
if (bindingProperties.getPartitionSelectorExpression() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION,
|
||||
bindingProperties.getPartitionSelectorExpression());
|
||||
}
|
||||
if (bindingProperties.getPartitionCount() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.NEXT_MODULE_COUNT,
|
||||
Integer.toString(bindingProperties.getPartitionCount()));
|
||||
}
|
||||
if (bindingProperties.getNextModuleConcurrency() != null) {
|
||||
producerProperties.setProperty(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY,
|
||||
Integer.toString(bindingProperties.getNextModuleConcurrency()));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
private void updateConsumerPartitionProperties(String inputChannelName, Properties consumerProperties) {
|
||||
BindingProperties bindingProperties = this.bindings.get(inputChannelName);
|
||||
if (bindingProperties != null) {
|
||||
if (isPartitionedConsumer(inputChannelName)) {
|
||||
consumerProperties.setProperty(BinderPropertyKeys.COUNT,
|
||||
Integer.toString(getInstanceCount()));
|
||||
consumerProperties.setProperty(BinderPropertyKeys.PARTITION_INDEX,
|
||||
Integer.toString(getInstanceIndex()));
|
||||
}
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (conversionService == null) {
|
||||
conversionService = applicationContext.getBean(IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
|
||||
}
|
||||
}
|
||||
|
||||
public String getBinder(String channelName) {
|
||||
if (!bindings.containsKey(channelName)) {
|
||||
return null;
|
||||
}
|
||||
return bindings.get(channelName).getBinder();
|
||||
return getBindingProperties(channelName).getBinder();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,15 +188,72 @@ public class ChannelBindingServiceProperties {
|
||||
properties.put("instanceCount", String.valueOf(getInstanceCount()));
|
||||
properties.put("defaultBinder", getDefaultBinder());
|
||||
properties.put("dynamicDestinations", getDynamicDestinations());
|
||||
// Add Bindings properties
|
||||
for (Map.Entry<String, BindingProperties> entry : getBindings().entrySet()) {
|
||||
for (Map.Entry<String, BindingProperties> entry : bindings.entrySet()) {
|
||||
properties.put(entry.getKey(), entry.getValue().toString());
|
||||
}
|
||||
// Add Binder config properties
|
||||
for (Map.Entry<String, BinderProperties> entry : binders.entrySet()) {
|
||||
properties.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public <T extends ConsumerProperties> T getConsumerProperties(String inputChannelName, Class<T> beanClass) {
|
||||
Assert.notNull(inputChannelName, "The input channel name cannot be null");
|
||||
Assert.notNull(beanClass, "The bean class cannot be null");
|
||||
T consumerProperties = populateProperties(inputChannelName, beanClass, consumerDefaults);
|
||||
consumerProperties.setInstanceCount(this.instanceCount);
|
||||
consumerProperties.setInstanceIndex(this.instanceIndex);
|
||||
return consumerProperties;
|
||||
}
|
||||
|
||||
|
||||
public <T extends ProducerProperties> T getProducerProperties(String outputChannelName, Class<T> beanClass) {
|
||||
Assert.notNull(outputChannelName, "The output channel name cannot be null");
|
||||
Assert.notNull(beanClass, "The bean class cannot be null");
|
||||
T producerProperties = populateProperties(outputChannelName, beanClass, producerDefaults);
|
||||
return producerProperties;
|
||||
}
|
||||
|
||||
|
||||
private <C> C populateProperties(String channelName, Class<C> propertiesClass, Properties defaults) {
|
||||
C beanInstance;
|
||||
try {
|
||||
beanInstance = propertiesClass.newInstance();
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new BeanInitializationException(e.getMessage());
|
||||
}
|
||||
// bind defaults first
|
||||
RelaxedDataBinder dataBinder = new RelaxedDataBinder(beanInstance);
|
||||
dataBinder.setIgnoreUnknownFields(ignoreUnknownProperties);
|
||||
dataBinder.setDisallowedFields(bindingPropertyFields);
|
||||
dataBinder.bind(new MutablePropertyValues(defaults));
|
||||
// bind configured properties next, if available
|
||||
if (applicationContext != null && applicationContext.getEnvironment() != null) {
|
||||
dataBinder = new RelaxedDataBinder(beanInstance, "spring.cloud.stream.bindings." + channelName);
|
||||
dataBinder.setConversionService(conversionService);
|
||||
dataBinder.setIgnoreUnknownFields(ignoreUnknownProperties);
|
||||
dataBinder.setDisallowedFields(bindingPropertyFields);
|
||||
dataBinder.bind(new PropertySourcesPropertyValues(applicationContext.getEnvironment().getPropertySources()));
|
||||
}
|
||||
return beanInstance;
|
||||
}
|
||||
|
||||
public BindingProperties getBindingProperties(String channelName) {
|
||||
BindingProperties bindingProperties = bindings.containsKey(channelName) ?
|
||||
bindings.get(channelName) : new BindingProperties();
|
||||
return bindingProperties;
|
||||
}
|
||||
|
||||
public String getGroup(String channelName) {
|
||||
return getBindingProperties(channelName).getGroup();
|
||||
}
|
||||
|
||||
public String getBindingDestination(String channelName) {
|
||||
BindingProperties bindingProperties = getBindingProperties(channelName);
|
||||
if (bindingProperties != null && StringUtils.hasText(bindingProperties.getDestination())) {
|
||||
return bindingProperties.getDestination();
|
||||
}
|
||||
return channelName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.config.IntegrationConverter;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,7 @@ import org.springframework.integration.context.IntegrationContextUtils;
|
||||
public class SpelExpressionConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
@ConfigurationPropertiesBinding @IntegrationConverter
|
||||
public Converter<String, Expression> spelConverter() {
|
||||
return new SpelConverter();
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
|
||||
import org.springframework.cloud.stream.binding.Bindable;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
|
||||
import org.springframework.cloud.stream.binding.Bindable;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@@ -68,7 +68,9 @@ public class ChannelsEndpoint extends AbstractEndpoint<Map<String,Object>> {
|
||||
|
||||
@JsonInclude(value = Include.NON_DEFAULT)
|
||||
public static class ChannelsMetaData {
|
||||
|
||||
private Map<String, BindingProperties> inputs = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, BindingProperties> outputs = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, BindingProperties> getInputs() {
|
||||
@@ -88,4 +90,4 @@ public class ChannelsEndpoint extends AbstractEndpoint<Map<String,Object>> {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
@@ -55,10 +53,10 @@ public class ArbitraryInterfaceBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testArbitraryInterfaceChannelsBound() {
|
||||
verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(fooChannels.foo()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(fooChannels.bar()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("someQueue.2"), eq(fooChannels.baz()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("someQueue.3"), eq(fooChannels.qux()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(fooChannels.foo()), Mockito.<ConsumerProperties>any());
|
||||
verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(fooChannels.bar()), Mockito.<ConsumerProperties>any());
|
||||
verify(binder).bindProducer(eq("someQueue.2"), eq(fooChannels.baz()), Mockito.<ProducerProperties>any());
|
||||
verify(binder).bindProducer(eq("someQueue.3"), eq(fooChannels.qux()), Mockito.<ProducerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ public class ArbitraryInterfaceBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testArbitraryInterfaceChannelsBound() {
|
||||
verify(binder).bindConsumer(eq("foo"), anyString(), eq(fooChannels.foo()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("bar"), anyString(), eq(fooChannels.bar()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("baz"), eq(fooChannels.baz()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("qux"), eq(fooChannels.qux()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("foo"), anyString(), eq(fooChannels.foo()), Mockito.<ConsumerProperties>any());
|
||||
verify(binder).bindConsumer(eq("bar"), anyString(), eq(fooChannels.bar()), Mockito.<ConsumerProperties>any());
|
||||
verify(binder).bindProducer(eq("baz"), eq(fooChannels.baz()), Mockito.<ProducerProperties>any());
|
||||
verify(binder).bindProducer(eq("qux"), eq(fooChannels.qux()), Mockito.<ProducerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -68,18 +67,19 @@ public class BinderAwareChannelResolverTests {
|
||||
|
||||
private volatile BinderAwareChannelResolver resolver;
|
||||
|
||||
private volatile Binder<MessageChannel> binder;
|
||||
private volatile Binder<MessageChannel, ConsumerProperties, ProducerProperties> binder;
|
||||
|
||||
@Before
|
||||
public void setupContext() throws Exception {
|
||||
this.binder = new TestBinder();
|
||||
BinderFactory binderFactory = new BinderFactory<MessageChannel>() {
|
||||
|
||||
@Override
|
||||
public Binder<MessageChannel> getBinder(String configurationName) {
|
||||
public Binder<MessageChannel, ConsumerProperties, ProducerProperties> getBinder(String configurationName) {
|
||||
return binder;
|
||||
}
|
||||
};
|
||||
this.resolver = new BinderAwareChannelResolver(binderFactory, null, new DynamicDestinationsBindable());
|
||||
this.resolver = new BinderAwareChannelResolver(binderFactory, new ChannelBindingServiceProperties(), new DynamicDestinationsBindable());
|
||||
this.resolver.setBeanFactory(context.getBeanFactory());
|
||||
context.getBeanFactory().registerSingleton("channelResolver",
|
||||
this.resolver);
|
||||
@@ -94,7 +94,7 @@ public class BinderAwareChannelResolverTests {
|
||||
MessageChannel registered = resolver.resolveDestination("foo");
|
||||
DirectChannel testChannel = new DirectChannel();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final List<Message<?>> received = new ArrayList<Message<?>>();
|
||||
final List<Message<?>> received = new ArrayList<>();
|
||||
testChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
@@ -103,7 +103,7 @@ public class BinderAwareChannelResolverTests {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
binder.bindConsumer("foo", null, testChannel, null);
|
||||
binder.bindConsumer("foo", null, testChannel, new ConsumerProperties());
|
||||
assertEquals(0, received.size());
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
try {
|
||||
@@ -125,25 +125,24 @@ public class BinderAwareChannelResolverTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public void propertyPassthrough() {
|
||||
ChannelBindingServiceProperties bindingServiceProperties = new ChannelBindingServiceProperties();
|
||||
DynamicDestinationsBindable dynamicDestinationsBindable = new DynamicDestinationsBindable();
|
||||
Map<String, BindingProperties> bindings = new HashMap<String, BindingProperties>();
|
||||
BindingProperties bindingProperties = new BindingProperties();
|
||||
bindingProperties.setContentType("text/plain");
|
||||
bindings.put("foo", bindingProperties);
|
||||
Map<String, BindingProperties> bindings = new HashMap<>();
|
||||
BindingProperties genericProperties = new BindingProperties();
|
||||
bindings.put("foo", genericProperties);
|
||||
bindingServiceProperties.setBindings(bindings);
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel> binder = mock(Binder.class);
|
||||
Binder<MessageChannel> binder2 = mock(Binder.class);
|
||||
BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
|
||||
Binder binder = mock(Binder.class);
|
||||
Binder binder2 = mock(Binder.class);
|
||||
BinderFactory<MessageChannel> mockBinderFactory = Mockito.mock(BinderFactory.class);
|
||||
Binding<MessageChannel> fooBinding = Mockito.mock(Binding.class);
|
||||
Binding<MessageChannel> barBinding = Mockito.mock(Binding.class);
|
||||
when(binder.bindProducer(
|
||||
matches("foo"), any(DirectChannel.class), any(Properties.class))).thenReturn(fooBinding);
|
||||
matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(fooBinding);
|
||||
when(binder2.bindProducer(
|
||||
matches("bar"), any(DirectChannel.class), any(Properties.class))).thenReturn(barBinding);
|
||||
matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(barBinding);
|
||||
when(mockBinderFactory.getBinder(null)).thenReturn(binder);
|
||||
when(mockBinderFactory.getBinder("someTransport")).thenReturn(binder2);
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -152,10 +151,10 @@ public class BinderAwareChannelResolverTests {
|
||||
BeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
resolver.setBeanFactory(beanFactory);
|
||||
MessageChannel resolved = resolver.resolveDestination("foo");
|
||||
verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), any(Properties.class));
|
||||
verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), any(ProducerProperties.class));
|
||||
assertSame(resolved, beanFactory.getBean("foo"));
|
||||
resolved = resolver.resolveDestination("someTransport:bar");
|
||||
verify(binder2).bindProducer(eq("bar"), any(MessageChannel.class), any(Properties.class));
|
||||
verify(binder2).bindProducer(eq("bar"), any(MessageChannel.class), any(ProducerProperties.class));
|
||||
assertSame(resolved, beanFactory.getBean("someTransport:bar"));
|
||||
assertTrue("Dynamic bindable should have two destination names", dynamicDestinationsBindable.getOutputs().size() == 2);
|
||||
assertTrue("Dynamic bindable should have the destination name 'foo'", dynamicDestinationsBindable.getOutputs().contains("foo"));
|
||||
@@ -165,13 +164,12 @@ public class BinderAwareChannelResolverTests {
|
||||
/**
|
||||
* A simple test binder that creates queues for the destinations. Ignores groups.
|
||||
*/
|
||||
private class TestBinder implements Binder<MessageChannel> {
|
||||
private class TestBinder implements Binder<MessageChannel, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private final Map<String, DirectChannel> destinations = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget,
|
||||
Properties properties) {
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget, ConsumerProperties properties) {
|
||||
synchronized (destinations) {
|
||||
if (!destinations.containsKey(name)) {
|
||||
destinations.put(name, new DirectChannel());
|
||||
@@ -184,7 +182,7 @@ public class BinderAwareChannelResolverTests {
|
||||
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) {
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, ProducerProperties properties) {
|
||||
synchronized (destinations) {
|
||||
if (!destinations.containsKey(name)) {
|
||||
destinations.put(name, new DirectChannel());
|
||||
|
||||
@@ -38,14 +38,13 @@ import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.binder.stub1.StubBinder1;
|
||||
import org.springframework.cloud.stream.binder.stub1.StubBinder1Configuration;
|
||||
import org.springframework.cloud.stream.binder.stub2.StubBinder2;
|
||||
import org.springframework.cloud.stream.binder.stub2.StubBinder2ConfigurationA;
|
||||
import org.springframework.cloud.stream.binder.stub2.StubBinder2ConfigurationB;
|
||||
import org.springframework.cloud.stream.config.BinderFactoryConfiguration;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -207,7 +206,7 @@ public class BinderFactoryConfigurationTests {
|
||||
}
|
||||
|
||||
@Import({BinderFactoryConfiguration.class, PropertyPlaceholderAutoConfiguration.class})
|
||||
@EnableConfigurationProperties(ChannelBindingServiceProperties.class)
|
||||
@EnableBinding
|
||||
public static class SimpleApplication {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.collection.IsArrayContainingInOrder.arrayContaining;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class DefaultSettingsTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testDefaultSettings() {
|
||||
ConfigurableApplicationContext applicationContext = createBuilder().run(
|
||||
"--spring.cloud.stream.bindings.foo.destination=fooDest",
|
||||
"--spring.cloud.stream.bindings.bar.destination=barDest",
|
||||
"--spring.cloud.stream.bindings.baz.destination=bazDest",
|
||||
"--spring.cloud.stream.bindings.qux.destination=quxDest",
|
||||
"--spring.cloud.stream.bindings.foo.group=fooBarGroup",
|
||||
"--spring.cloud.stream.bindings.bar.group=fooBarGroup",
|
||||
"--spring.cloud.stream.consumerDefaults.concurrency=2",
|
||||
"--spring.cloud.stream.producerDefaults.requiredGroups=quxbazReq1,quxbazReq2");
|
||||
|
||||
Binder binder = applicationContext.getBean(Binder.class);
|
||||
FooChannels fooChannels = applicationContext.getBean(FooChannels.class);
|
||||
|
||||
ArgumentCaptor<ConsumerProperties> fooConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class);
|
||||
verify(binder).bindConsumer(eq("fooDest"), eq("fooBarGroup"), same(fooChannels.foo()),
|
||||
fooConsumerProperties.capture());
|
||||
assertThat(fooConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2));
|
||||
ArgumentCaptor<ConsumerProperties> barConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class);
|
||||
verify(binder).bindConsumer(eq("barDest"), eq("fooBarGroup"), same(fooChannels.bar()),
|
||||
barConsumerProperties.capture());
|
||||
assertThat(barConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2));
|
||||
|
||||
ArgumentCaptor<ProducerProperties> bazProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("bazDest"), same(fooChannels.baz()), bazProducerProperties.capture());
|
||||
assertThat(bazProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2"));
|
||||
|
||||
ArgumentCaptor<ProducerProperties> quxProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("quxDest"), same(fooChannels.qux()), quxProducerProperties.capture());
|
||||
assertThat(quxProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2"));
|
||||
|
||||
verifyNoMoreInteractions(binder);
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testDefaultSettingsOverridden() {
|
||||
ConfigurableApplicationContext applicationContext = createBuilder().run(
|
||||
"--spring.cloud.stream.bindings.foo.destination=fooDest",
|
||||
"--spring.cloud.stream.bindings.bar.destination=barDest",
|
||||
"--spring.cloud.stream.bindings.baz.destination=bazDest",
|
||||
"--spring.cloud.stream.bindings.qux.destination=quxDest",
|
||||
"--spring.cloud.stream.bindings.foo.group=fooBarGroup",
|
||||
"--spring.cloud.stream.bindings.bar.group=fooBarGroup",
|
||||
"--spring.cloud.stream.consumerDefaults.concurrency=2",
|
||||
"--spring.cloud.stream.bindings.bar.concurrency=4",
|
||||
"--spring.cloud.stream.producerDefaults.requiredGroups=quxbazReq1,quxbazReq2",
|
||||
"--spring.cloud.stream.bindings.qux.requiredGroups=quxbazReq3,quxbazReq4");
|
||||
|
||||
Binder binder = applicationContext.getBean(Binder.class);
|
||||
FooChannels fooChannels = applicationContext.getBean(FooChannels.class);
|
||||
|
||||
ArgumentCaptor<ConsumerProperties> fooConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class);
|
||||
verify(binder).bindConsumer(eq("fooDest"), eq("fooBarGroup"), same(fooChannels.foo()),
|
||||
fooConsumerProperties.capture());
|
||||
assertThat(fooConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2));
|
||||
ArgumentCaptor<ConsumerProperties> barConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class);
|
||||
verify(binder).bindConsumer(eq("barDest"), eq("fooBarGroup"), same(fooChannels.bar()),
|
||||
barConsumerProperties.capture());
|
||||
assertThat(barConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(4));
|
||||
|
||||
ArgumentCaptor<ProducerProperties> bazProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("bazDest"), same(fooChannels.baz()), bazProducerProperties.capture());
|
||||
assertThat(bazProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2"));
|
||||
|
||||
ArgumentCaptor<ProducerProperties> quxProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("quxDest"), same(fooChannels.qux()), quxProducerProperties.capture());
|
||||
assertThat(quxProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq3", "quxbazReq4"));
|
||||
|
||||
verifyNoMoreInteractions(binder);
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
private SpringApplicationBuilder createBuilder() {
|
||||
return new SpringApplicationBuilder(TestFooChannels.class)
|
||||
.web(false);
|
||||
}
|
||||
|
||||
|
||||
@EnableBinding(FooChannels.class)
|
||||
@EnableAutoConfiguration
|
||||
@Import(MockBinderRegistryConfiguration.class)
|
||||
public static class TestFooChannels {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -48,10 +48,10 @@ public class ErrorBindingTests {
|
||||
BinderFactory<?> binderFactory = applicationContext.getBean(BinderFactory.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel> binder = (Binder<MessageChannel>) binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(Properties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(Properties.class));
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class));
|
||||
Mockito.verifyNoMoreInteractions(binder);
|
||||
applicationContext.close();
|
||||
}
|
||||
@@ -64,14 +64,14 @@ public class ErrorBindingTests {
|
||||
BinderFactory<?> binderFactory = applicationContext.getBean(BinderFactory.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel> binder = (Binder<MessageChannel>) binderFactory.getBinder(null);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
|
||||
MessageChannel errorChannel = applicationContext.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME,
|
||||
MessageChannel.class);
|
||||
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(Properties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(Properties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("foo"), same(errorChannel), any(Properties.class));
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("foo"), same(errorChannel), any(ProducerProperties.class));
|
||||
Mockito.verifyNoMoreInteractions(binder);
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -52,7 +50,7 @@ public class InputOutputBindingOrderTest {
|
||||
Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null);
|
||||
Processor processor = applicationContext.getBean(Processor.class);
|
||||
// input is bound after the context has been started
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<ConsumerProperties>any());
|
||||
SomeLifecycle someLifecycle = applicationContext.getBean(SomeLifecycle.class);
|
||||
assertTrue(someLifecycle.isRunning());
|
||||
applicationContext.close();
|
||||
@@ -84,7 +82,7 @@ public class InputOutputBindingOrderTest {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void start() {
|
||||
verify(this.binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.<Properties>any());
|
||||
verify(this.binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.<ProducerProperties>any());
|
||||
// input was not bound yet
|
||||
verifyNoMoreInteractions(this.binder);
|
||||
this.running = true;
|
||||
|
||||
@@ -54,8 +54,8 @@ public class ProcessorBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("testtock.0"), anyString(), eq(testProcessor.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("testtock.1"), eq(testProcessor.output()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("testtock.0"), anyString(), eq(testProcessor.input()), Mockito.<ConsumerProperties>any());
|
||||
verify(binder).bindProducer(eq("testtock.1"), eq(testProcessor.output()), Mockito.<ProducerProperties>any());
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
|
||||
@@ -53,8 +53,8 @@ public class ProcessorBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<Properties>any());
|
||||
Mockito.verify(binder).bindProducer(eq("output"), eq(processor.output()), Mockito.<Properties>any());
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<ConsumerProperties>any());
|
||||
Mockito.verify(binder).bindProducer(eq("output"), eq(processor.output()), Mockito.<ProducerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SinkBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("testtock"), anyString(), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("testtock"), anyString(), eq(testSink.input()), Mockito.<ConsumerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SinkBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(testSink.input()), Mockito.<ConsumerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,7 @@ public class SourceBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindProducer(eq("testtock"), eq(testSource.output()), Mockito.<Properties>any());
|
||||
//Check error channel binding
|
||||
verify(binder).bindProducer(eq("testtock"), eq(testSource.output()), Mockito.<ProducerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
@@ -53,7 +51,7 @@ public class SourceBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindProducer(eq("output"), eq(testSource.output()), Mockito.<Properties>any());
|
||||
verify(binder).bindProducer(eq("output"), eq(testSource.output()), Mockito.<ProducerProperties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.stub1;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class StubBinder1 implements Binder<Object> {
|
||||
public class StubBinder1 implements Binder<Object, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -38,12 +38,12 @@ public class StubBinder1 implements Binder<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) {
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, ProducerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class StubBinder1Configuration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("binder1")
|
||||
public Binder<?> binder() {
|
||||
public Binder<?, ?, ?> binder() {
|
||||
return new StubBinder1();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.stub2;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class StubBinder2 implements Binder<Object> {
|
||||
public class StubBinder2 implements Binder<Object, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final StubBinder2Dependency stubBinder2Dependency;
|
||||
@@ -35,12 +35,12 @@ public class StubBinder2 implements Binder<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) {
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, ProducerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
public class StubBinder2ConfigurationA {
|
||||
|
||||
@Bean
|
||||
public Binder<?> binder(StubBinder2Dependency dependency) {
|
||||
public Binder<?, ?, ?> binder(StubBinder2Dependency dependency) {
|
||||
return new StubBinder2(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isNull;
|
||||
import static org.mockito.Matchers.matches;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -49,7 +51,9 @@ import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderConfiguration;
|
||||
import org.springframework.cloud.stream.binder.BinderType;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.cloud.stream.utils.MockBinderConfiguration;
|
||||
@@ -78,19 +82,19 @@ public class ChannelBindingServiceTests {
|
||||
new DefaultBinderFactory<>(Collections.singletonMap("mock",
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
new Properties(), true)));
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
Binder binder = binderFactory.getBinder("mock");
|
||||
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
@SuppressWarnings("unchecked")
|
||||
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
|
||||
when(binder.bindConsumer("foo", null, inputChannel, new Properties()))
|
||||
when(binder.bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)))
|
||||
.thenReturn(mockBinding);
|
||||
Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, inputChannelName);
|
||||
assertThat(bindings.size(), is(1));
|
||||
Binding<MessageChannel> binding = bindings.iterator().next();
|
||||
assertThat(binding, sameInstance(mockBinding));
|
||||
service.unbindConsumers(inputChannelName);
|
||||
verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName));
|
||||
verify(binder).bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class));
|
||||
verify(binding).unbind();
|
||||
binderFactory.destroy();
|
||||
}
|
||||
@@ -112,7 +116,7 @@ public class ChannelBindingServiceTests {
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
new Properties(), true)));
|
||||
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
Binder binder = binderFactory.getBinder("mock");
|
||||
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
|
||||
@@ -121,9 +125,9 @@ public class ChannelBindingServiceTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
Binding<MessageChannel> mockBinding2 = Mockito.mock(Binding.class);
|
||||
|
||||
when(binder.bindConsumer("foo", null, inputChannel, new Properties()))
|
||||
when(binder.bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)))
|
||||
.thenReturn(mockBinding1);
|
||||
when(binder.bindConsumer("bar", null, inputChannel, new Properties()))
|
||||
when(binder.bindConsumer(eq("bar"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)))
|
||||
.thenReturn(mockBinding2);
|
||||
|
||||
Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, "input");
|
||||
@@ -138,8 +142,8 @@ public class ChannelBindingServiceTests {
|
||||
|
||||
service.unbindConsumers("input");
|
||||
|
||||
verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName));
|
||||
verify(binder).bindConsumer("bar", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName));
|
||||
verify(binder).bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class));
|
||||
verify(binder).bindConsumer(eq("bar"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class));
|
||||
verify(binding1).unbind();
|
||||
verify(binding2).unbind();
|
||||
|
||||
@@ -160,12 +164,12 @@ public class ChannelBindingServiceTests {
|
||||
new DefaultBinderFactory<>(Collections.singletonMap("mock",
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
new Properties(), true)));
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
Binder binder = binderFactory.getBinder("mock");
|
||||
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
@SuppressWarnings("unchecked")
|
||||
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
|
||||
when(binder.bindConsumer("foo", "fooGroup", inputChannel, new Properties()))
|
||||
when(binder.bindConsumer(eq("foo"), eq("fooGroup"), same(inputChannel), any(ConsumerProperties.class)))
|
||||
.thenReturn(mockBinding);
|
||||
Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, inputChannelName);
|
||||
assertThat(bindings.size(), is(1));
|
||||
@@ -173,7 +177,7 @@ public class ChannelBindingServiceTests {
|
||||
assertThat(binding, sameInstance(mockBinding));
|
||||
|
||||
service.unbindConsumers(inputChannelName);
|
||||
verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName));
|
||||
verify(binder).bindConsumer(eq("foo"), eq(props.getGroup()), same(inputChannel), any(ConsumerProperties.class));
|
||||
verify(binding).unbind();
|
||||
binderFactory.destroy();
|
||||
}
|
||||
@@ -187,15 +191,14 @@ public class ChannelBindingServiceTests {
|
||||
new DefaultBinderFactory<>(Collections.singletonMap("mock",
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
new Properties(), true)));
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
Binder binder = binderFactory.getBinder("mock");
|
||||
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
@SuppressWarnings("unchecked")
|
||||
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
final AtomicReference<MessageChannel> dynamic = new AtomicReference<>();
|
||||
when(binder.bindProducer(
|
||||
matches("bar"), any(DirectChannel.class), any(Properties.class))).thenReturn(mockBinding);
|
||||
when(binder.bindProducer(matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(mockBinding);
|
||||
BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(binderFactory, properties, dynamicDestinationsBindable);
|
||||
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class);
|
||||
when(beanFactory.getBean("mock:bar", MessageChannel.class))
|
||||
@@ -220,7 +223,7 @@ public class ChannelBindingServiceTests {
|
||||
resolver.setBeanFactory(beanFactory);
|
||||
MessageChannel resolved = resolver.resolveDestination("mock:bar");
|
||||
assertThat(resolved, sameInstance(dynamic.get()));
|
||||
verify(binder).bindProducer(eq("bar"), eq(dynamic.get()), any(Properties.class));
|
||||
verify(binder).bindProducer(eq("bar"), eq(dynamic.get()), any(ProducerProperties.class));
|
||||
properties.setDynamicDestinations(new String[] { "mock:bar" });
|
||||
resolved = resolver.resolveDestination("mock:bar");
|
||||
assertThat(resolved, sameInstance(dynamic.get()));
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.binding;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class PropertiesClassResolutionTests {
|
||||
|
||||
@Test
|
||||
public void testDefaultResolution() {
|
||||
SimpleBinderImplementation testBinder = new SimpleBinderImplementation();
|
||||
Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinderImplementorWithCustomTypes() {
|
||||
BinderImplementationWithCustomTypes testBinder = new BinderImplementationWithCustomTypes();
|
||||
Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendsAbstractBinderWithDefaultTypes() {
|
||||
ExtendsAbstractBinderWithDefaultTypes testBinder = new ExtendsAbstractBinderWithDefaultTypes();
|
||||
Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendsAbstractBinderWithCustomTypes() {
|
||||
ExtendsAbstractBinderWithCustomTypes testBinder = new ExtendsAbstractBinderWithCustomTypes();
|
||||
Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenericBinderWithDefaultTypes() {
|
||||
GenericBinderWithDefaultTypes testBinder = new GenericBinderWithDefaultTypes();
|
||||
Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenericBinderWithCustomTypes() {
|
||||
GenericBinderWithCustomTypes testBinder = new GenericBinderWithCustomTypes();
|
||||
Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder));
|
||||
Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder));
|
||||
}
|
||||
|
||||
private class SimpleBinderImplementation implements Binder<Object, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties consumerProperties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, ProducerProperties producerProperties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class BinderImplementationWithCustomTypes implements Binder<Object, SubclassConsumerProperties, SubclassProducerProperties> {
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, SubclassConsumerProperties consumerProperties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, SubclassProducerProperties producerProperties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtendsAbstractBinderWithDefaultTypes extends AbstractBinder<Object, ConsumerProperties, ProducerProperties> {
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindConsumer(String name, String group, Object inputTarget, ConsumerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindProducer(String name, Object outboundBindTarget, ProducerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ExtendsAbstractBinderWithCustomTypes extends AbstractBinder<Object, SubclassConsumerProperties, SubclassProducerProperties> {
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindConsumer(String name, String group, Object inputTarget, SubclassConsumerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindProducer(String name, Object outboundBindTarget, SubclassProducerProperties properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class GenericBinderWithDefaultTypes<C extends ConsumerProperties, P extends ProducerProperties> extends AbstractBinder<Object, C, P> {
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindConsumer(String name, String group, Object inputTarget, C properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindProducer(String name, Object outboundBindTarget, P properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class GenericBinderWithCustomTypes<C extends SubclassConsumerProperties, P extends SubclassProducerProperties> extends AbstractBinder<Object, C, P> {
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindConsumer(String name, String group, Object inputTarget, C properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<Object> doBindProducer(String name, Object outboundBindTarget, P properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class SubclassConsumerProperties extends ConsumerProperties {
|
||||
|
||||
}
|
||||
|
||||
private class SubclassProducerProperties extends ProducerProperties {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,23 +16,25 @@
|
||||
|
||||
package org.springframework.cloud.stream.config;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
@@ -68,8 +70,9 @@ public class SpelExpressionConverterConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(SpelExpressionConverterConfiguration.class)
|
||||
@EnableIntegration
|
||||
@EnableBinding
|
||||
@EnableAutoConfiguration
|
||||
@Import(MockBinderRegistryConfiguration.class)
|
||||
@EnableConfigurationProperties(Pojo.class)
|
||||
public static class Config {
|
||||
|
||||
|
||||
@@ -22,8 +22,6 @@ import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -36,7 +34,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.Bindings;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -61,10 +59,10 @@ public class PartitionedConsumerTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testBindingPartitionedConsumer() {
|
||||
ArgumentCaptor<Properties> argumentCaptor = ArgumentCaptor.forClass(Properties.class);
|
||||
ArgumentCaptor<ConsumerProperties> argumentCaptor = ArgumentCaptor.forClass(ConsumerProperties.class);
|
||||
verify(binder).bindConsumer(eq("partIn"), anyString(), eq(testSink.input()), argumentCaptor.capture());
|
||||
Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.PARTITION_INDEX), equalTo("0"));
|
||||
Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.COUNT), equalTo("2"));
|
||||
Assert.assertThat(argumentCaptor.getValue().getInstanceIndex(), equalTo(0));
|
||||
Assert.assertThat(argumentCaptor.getValue().getInstanceCount(), equalTo(2));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@@ -77,13 +75,10 @@ public class PartitionedConsumerTest {
|
||||
|
||||
}
|
||||
|
||||
class PropertiesArgumentMatcher extends ArgumentMatcher<Properties> {
|
||||
class PropertiesArgumentMatcher extends ArgumentMatcher<ConsumerProperties> {
|
||||
@Override
|
||||
public boolean matches(Object argument) {
|
||||
if (!(argument instanceof Properties)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return argument instanceof ConsumerProperties;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -34,7 +32,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.Bindings;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -59,10 +57,10 @@ public class PartitionedProducerTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testBindingPartitionedProducer() {
|
||||
ArgumentCaptor<Properties> argumentCaptor = ArgumentCaptor.forClass(Properties.class);
|
||||
ArgumentCaptor<ProducerProperties> argumentCaptor = ArgumentCaptor.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("partOut"), eq(testSource.output()), argumentCaptor.capture());
|
||||
Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.NEXT_MODULE_COUNT), equalTo("3"));
|
||||
Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION),
|
||||
Assert.assertThat(argumentCaptor.getValue().getPartitionCount(), equalTo(3));
|
||||
Assert.assertThat(argumentCaptor.getValue().getPartitionKeyExpression().getExpressionString(),
|
||||
equalTo("payload"));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
public class MockBinderConfiguration {
|
||||
|
||||
@Bean
|
||||
public Binder<?> binder() {
|
||||
public Binder<?, ?, ?> binder() {
|
||||
return Mockito.mock(Binder.class, Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class MockBinderRegistryConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binder<?> defaultBinder(BinderFactory<MessageChannel> binderFactory) {
|
||||
public Binder<?,?,?> defaultBinder(BinderFactory<MessageChannel> binderFactory) {
|
||||
return binderFactory.getBinder(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.stream.bindings.foo=someQueue.0
|
||||
spring.cloud.stream.bindings.bar=someQueue.1
|
||||
spring.cloud.stream.bindings.baz=someQueue.2
|
||||
spring.cloud.stream.bindings.qux=someQueue.3
|
||||
spring.cloud.stream.bindings.foo.destination=someQueue.0
|
||||
spring.cloud.stream.bindings.bar.destination=someQueue.1
|
||||
spring.cloud.stream.bindings.baz.destination=someQueue.2
|
||||
spring.cloud.stream.bindings.qux.destination=someQueue.3
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spring.cloud.stream.bindings.input=topic:testtock.0
|
||||
spring.cloud.stream.bindings.output=topic:testtock.1
|
||||
@@ -1,2 +1,2 @@
|
||||
spring.cloud.stream.bindings.input=testtock.0
|
||||
spring.cloud.stream.bindings.output=testtock.1
|
||||
spring.cloud.stream.bindings.input.destination=testtock.0
|
||||
spring.cloud.stream.bindings.output.destination=testtock.1
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
spring.cloud.stream.bindings.input.destination=topic:testpubsub
|
||||
spring.cloud.stream.bindings.input.group=tgroup
|
||||
Reference in New Issue
Block a user