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:
Marius Bogoevici
2016-03-06 20:55:44 -05:00
committed by Mark Fisher
parent 6bbf688d63
commit 9b0c4bd627
84 changed files with 1994 additions and 3287 deletions

View File

@@ -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;
}
}

View File

@@ -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)) {

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -1,2 +1,2 @@
kafka:\
org.springframework.cloud.stream.binder.kafka.config.KafkaServiceAutoConfiguration
org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration

View File

@@ -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);

View File

@@ -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();

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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 =

View File

@@ -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.
*/

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 =

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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.
*/

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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";
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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)) {

View File

@@ -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();

View File

@@ -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() + "}";
}

View File

@@ -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;
}
}

View File

@@ -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")

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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>> {
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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 {
}
}

View File

@@ -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 {
}
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -33,7 +33,7 @@ public class StubBinder1Configuration {
@Bean
@ConfigurationProperties("binder1")
public Binder<?> binder() {
public Binder<?, ?, ?> binder() {
return new StubBinder1();
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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()));

View File

@@ -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 {
}
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -41,7 +41,7 @@ public class MockBinderRegistryConfiguration {
}
@Bean
public Binder<?> defaultBinder(BinderFactory<MessageChannel> binderFactory) {
public Binder<?,?,?> defaultBinder(BinderFactory<MessageChannel> binderFactory) {
return binderFactory.getBinder(null);
}
}

View File

@@ -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

View File

@@ -1,2 +0,0 @@
spring.cloud.stream.bindings.input=topic:testtock.0
spring.cloud.stream.bindings.output=topic:testtock.1

View File

@@ -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

View File

@@ -1,2 +0,0 @@
spring.cloud.stream.bindings.input.destination=topic:testpubsub
spring.cloud.stream.bindings.input.group=tgroup