Binder simplification
* removed pub/sub methods from binder * consumer group is now a parameter of the remaining bindConsumer method * remove DynamicProducer from Binder * Move logic to create the dynamic channel to the channel resolver. * Return bindings from bind methods and use them for unbinding * Suffix for dlq Move All Rabbit Binder CleanUp to Test Bindera More RabbitMQ Binder Test Cleanup Clean up declarations for remaining tests. removed BinderUtils use Redis ZSET for consumer groups copyright dates AutoBindDLQ: Single DLQ Per Group When Partitioned Configure a single DLQ for each group for all partitions. Add DLX Exchange binding for each original queue routing key, including the partition. Fix DLQ Binding (Producer Side) Option was not allowed and the routing key was wrong. Add test to verify producers can be bound before consumers. `autoBindDLQ` must be set (or reset) on both sides for success.
This commit is contained in:
committed by
Marius Bogoevici
parent
60e44b530d
commit
c3758b9dc0
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -117,11 +117,13 @@ import scala.collection.Seq;
|
||||
* that better be greater than number of containers</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
@@ -184,12 +186,6 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
KafkaMessageChannelBinder.COMPRESSION_CODEC,
|
||||
}));
|
||||
|
||||
/**
|
||||
* The consumer group to use when achieving point to point semantics (that consumer group name is static and hence
|
||||
* shared by all containers).
|
||||
*/
|
||||
private static final String POINT_TO_POINT_SEMANTICS_CONSUMER_GROUP = "springXD";
|
||||
|
||||
private static final Set<Object> KAFKA_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.add(BinderPropertyKeys.MIN_PARTITION_COUNT)
|
||||
.build();
|
||||
@@ -210,25 +206,12 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
.add(BinderPropertyKeys.MIN_PARTITION_COUNT)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + concurrency.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_NAMED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.build();
|
||||
|
||||
private static final Set<Object> SUPPORTED_NAMED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Partitioning + kafka producer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.add(BinderPropertyKeys.DIRECT_BINDING_ALLOWED)
|
||||
.addAll(KAFKA_PRODUCER_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_COMPRESSION_PROPERTIES)
|
||||
@@ -246,6 +229,7 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
private final String zkAddress;
|
||||
|
||||
// -------- Default values for properties -------
|
||||
|
||||
private int defaultReplicationFactor = 1;
|
||||
|
||||
private String defaultCompressionCodec = DEFAULT_COMPRESSION_CODEC;
|
||||
@@ -306,7 +290,6 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
else {
|
||||
this.headersToMap = BinderHeaders.STANDARD_HEADERS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setOffsetStoreTopic(String offsetStoreTopic) {
|
||||
@@ -333,7 +316,6 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
this.offsetStoreMaxFetchSize = offsetStoreMaxFetchSize;
|
||||
}
|
||||
|
||||
|
||||
public void setOffsetUpdateTimeWindow(int offsetUpdateTimeWindow) {
|
||||
this.offsetUpdateTimeWindow = offsetUpdateTimeWindow;
|
||||
}
|
||||
@@ -460,99 +442,66 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, final MessageChannel moduleInputChannel, Properties properties) {
|
||||
// Point-to-point consumers reset at the earliest time, which allows them to catch up with all messages
|
||||
createKafkaConsumer(name, moduleInputChannel, properties, POINT_TO_POINT_SEMANTICS_CONSUMER_GROUP,
|
||||
OffsetRequest.EarliestTime());
|
||||
bindExistingProducerDirectlyIfPossible(name, moduleInputChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, String group, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) {
|
||||
// If the caller provides a group, use it; otherwise
|
||||
// usage of a different consumer group each time achieves pub-sub
|
||||
// but multiple instances of this binding will each get all messages
|
||||
// PubSub consumers reset at the latest time, which allows them to receive only messages sent after
|
||||
// they've been bound
|
||||
String consumerGroup = group == null ? UUID.randomUUID().toString() : group;
|
||||
createKafkaConsumer(name, inputChannel, properties, consumerGroup, OffsetRequest.LatestTime());
|
||||
return createKafkaConsumer(name, inputChannel, properties, consumerGroup, OffsetRequest.LatestTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
|
||||
public Binding<MessageChannel> bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
KafkaPropertiesAccessor producerPropertiesAccessor = new KafkaPropertiesAccessor(properties);
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_NAMED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
if (!bindNewProducerDirectlyIfPossible(name, (SubscribableChannel) moduleOutputChannel,
|
||||
producerPropertiesAccessor)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Using kafka topic for outbound: " + name);
|
||||
}
|
||||
|
||||
final String topicName = escapeTopicName(name);
|
||||
|
||||
int numPartitions = producerPropertiesAccessor.getNumberOfKafkaPartitionsForProducer();
|
||||
|
||||
Collection<Partition> partitions = ensureTopicCreated(topicName, numPartitions, defaultReplicationFactor);
|
||||
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>(
|
||||
topicName, byte[].class, byte[].class, BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setCompressionType(ProducerMetadata.CompressionType.valueOf(
|
||||
producerPropertiesAccessor.getCompressionCodec(this.defaultCompressionCodec)));
|
||||
producerMetadata.setBatchBytes(producerPropertiesAccessor.getBatchSize(this.defaultBatchSize));
|
||||
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);
|
||||
|
||||
try {
|
||||
final ProducerConfiguration<byte[], byte[]> producerConfiguration = new ProducerConfiguration<>(
|
||||
producerMetadata, producerFB.getObject());
|
||||
|
||||
MessageHandler handler = new SendingHandler(topicName, producerPropertiesAccessor,
|
||||
partitions.size(), producerConfiguration);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel,
|
||||
handler);
|
||||
consumer.setBeanFactory(this.getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
Binding producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer,
|
||||
producerPropertiesAccessor);
|
||||
addBinding(producerBinding);
|
||||
producerBinding.start();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Using kafka topic for outbound: " + name);
|
||||
}
|
||||
|
||||
}
|
||||
final String topicName = escapeTopicName(name);
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel outputChannel, Properties properties) {
|
||||
bindProducer(name, outputChannel, properties);
|
||||
}
|
||||
int numPartitions = producerPropertiesAccessor.getNumberOfKafkaPartitionsForProducer();
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
throw new UnsupportedOperationException("requestor binding is not supported by this binder");
|
||||
}
|
||||
Collection<Partition> partitions = ensureTopicCreated(topicName, numPartitions, defaultReplicationFactor);
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
throw new UnsupportedOperationException("replier binding is not supported by this binder");
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>(
|
||||
topicName, byte[].class, byte[].class, BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setCompressionType(ProducerMetadata.CompressionType.valueOf(
|
||||
producerPropertiesAccessor.getCompressionCodec(this.defaultCompressionCodec)));
|
||||
producerMetadata.setBatchBytes(producerPropertiesAccessor.getBatchSize(this.defaultBatchSize));
|
||||
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);
|
||||
|
||||
try {
|
||||
final ProducerConfiguration<byte[], byte[]> producerConfiguration = new ProducerConfiguration<>(
|
||||
producerMetadata, producerFB.getObject());
|
||||
|
||||
MessageHandler handler = new SendingHandler(topicName, producerPropertiesAccessor,
|
||||
partitions.size(), producerConfiguration);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel,
|
||||
handler);
|
||||
consumer.setBeanFactory(this.getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
Binding<MessageChannel> producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer,
|
||||
producerPropertiesAccessor);
|
||||
addBinding(producerBinding);
|
||||
producerBinding.start();
|
||||
return producerBinding;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,15 +559,10 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void createKafkaConsumer(String name, final MessageChannel moduleInputChannel, Properties properties,
|
||||
private Binding<MessageChannel> createKafkaConsumer(String name, final MessageChannel moduleInputChannel, Properties properties,
|
||||
String group, long referencePoint) {
|
||||
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_NAMED_CONSUMER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
}
|
||||
validateConsumerProperties(groupedName(name, group), properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
KafkaPropertiesAccessor accessor = new KafkaPropertiesAccessor(properties);
|
||||
|
||||
int maxConcurrency = accessor.getConcurrency(defaultConcurrency);
|
||||
@@ -702,12 +646,13 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
super.doStop();
|
||||
}
|
||||
};
|
||||
edc.setBeanName("inbound." + name);
|
||||
String groupedName = groupedName(name, group);
|
||||
edc.setBeanName("inbound." + groupedName);
|
||||
|
||||
Binding consumerBinding = Binding.forConsumer(name, edc, moduleInputChannel, accessor);
|
||||
Binding<MessageChannel> consumerBinding = Binding.forConsumer(name, group, edc, moduleInputChannel, accessor);
|
||||
addBinding(consumerBinding);
|
||||
consumerBinding.start();
|
||||
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
public KafkaMessageListenerContainer createMessageListenerContainer(Properties properties, String group,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -29,13 +29,12 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kafka.api.OffsetRequest;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Ignore;
|
||||
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.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.KafkaTestSupport;
|
||||
@@ -48,12 +47,15 @@ import org.springframework.integration.kafka.listener.MessageListener;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import kafka.api.OffsetRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link KafkaMessageChannelBinder}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@@ -137,8 +139,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
if (codec != null) {
|
||||
props.put(KafkaMessageChannelBinder.COMPRESSION_CODEC, codec);
|
||||
}
|
||||
binder.bindProducer("foo.0", moduleOutputChannel, props);
|
||||
binder.bindConsumer("foo.0", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, props);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -146,8 +148,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Message<?> inbound = moduleInputChannel.receive(2000);
|
||||
assertNotNull(inbound);
|
||||
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
|
||||
binder.unbindProducers("foo.0");
|
||||
binder.unbindConsumers("foo.0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,8 +168,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "10");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
binder.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -178,8 +180,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(10));
|
||||
binder.unbindProducers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -199,8 +201,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
consumerProps.put(BinderPropertyKeys.CONCURRENCY, "6");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
|
||||
binder.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProps);
|
||||
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();
|
||||
@@ -211,8 +213,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(6));
|
||||
binder.unbindProducers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -231,8 +233,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "6");
|
||||
consumerProps.put(BinderPropertyKeys.CONCURRENCY, "5");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
|
||||
binder.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProps);
|
||||
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();
|
||||
@@ -243,8 +245,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(6));
|
||||
binder.unbindProducers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -263,8 +265,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "3");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
binder.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -275,8 +277,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(5));
|
||||
binder.unbindProducers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -295,8 +297,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5");
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
binder.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -307,22 +309,8 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory().getPartitions(
|
||||
"foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions, hasSize(5));
|
||||
binder.unbindProducers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Override @Ignore("https://github.com/spring-cloud/spring-cloud-stream/issues/243")
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
}
|
||||
|
||||
@Override @Ignore("https://github.com/spring-cloud/spring-cloud-stream/issues/243")
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Kafka binder does not support direct binding")
|
||||
@Override
|
||||
public void testDirectBinding() throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -33,28 +33,24 @@ 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.cloud.stream.binder.TestUtils;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.channel.interceptor.WireTap;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
|
||||
@Ignore
|
||||
public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
@Override
|
||||
@@ -74,9 +70,9 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
binder.bindProducer("partJ.0", output, properties);
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partJ.0", output, properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
|
||||
properties.clear();
|
||||
@@ -85,15 +81,15 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
properties.put("partitionIndex", "0");
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0J");
|
||||
binder.bindConsumer("partJ.0", input0, properties);
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1J");
|
||||
binder.bindConsumer("partJ.0", input1, properties);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2J");
|
||||
binder.bindConsumer("partJ.0", input2, properties);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("partJ.0", "test", input2, properties);
|
||||
|
||||
output.send(new GenericMessage<>(new byte[]{(byte)0}));
|
||||
output.send(new GenericMessage<>(new byte[]{(byte)1}));
|
||||
@@ -112,8 +108,10 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
((byte[]) receive2.getPayload())[0]),
|
||||
containsInAnyOrder((byte)0, (byte)1, (byte)2));
|
||||
|
||||
binder.unbindConsumers("partJ.0");
|
||||
binder.unbindProducers("partJ.0");
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(input2Binding);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,9 +126,9 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
binder.bindProducer("part.0", output, properties);
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("part.0", output, properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
try {
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
@@ -146,15 +144,15 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
properties.put("count","3");
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
binder.bindConsumer("part.0", input0, properties);
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
binder.bindConsumer("part.0", input1, properties);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2S");
|
||||
binder.bindConsumer("part.0", input2, properties);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("part.0", "test", input2, properties);
|
||||
|
||||
Message<byte[]> message2 = MessageBuilder.withPayload(new byte[]{2})
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo")
|
||||
@@ -180,74 +178,10 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
((byte[]) receive2.getPayload())[0]),
|
||||
containsInAnyOrder((byte)0, (byte)1, (byte)2));
|
||||
|
||||
binder.unbindConsumers("part.0");
|
||||
binder.unbindProducers("part.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
@Ignore
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
// Test pub/sub by emulating how StreamPlugin handles taps
|
||||
DirectChannel tapChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
// Create the tap first
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null, null);
|
||||
|
||||
// Then create the stream
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
moduleOutputChannel.addInterceptor(new WireTap(tapChannel));
|
||||
binder.bindPubSubProducer(fooTapName, tapChannel, null);
|
||||
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null, null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar").build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
while (!success) {
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = moduleInputChannel.receive(5000);
|
||||
assertNotNull(inbound);
|
||||
assertEquals("foo", new String((byte[])inbound.getPayload()));
|
||||
Message<?> tapped1 = module2InputChannel.receive(5000);
|
||||
Message<?> tapped2 = module3InputChannel.receive(5000);
|
||||
if (tapped1 == null || tapped2 == null) {
|
||||
// listener may not have started
|
||||
assertFalse("Failed to receive tap after retry", retried);
|
||||
retried = true;
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
assertEquals("foo", new String((byte[]) tapped1.getPayload()));
|
||||
assertNull(tapped1.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo", new String((byte[])tapped2.getPayload()));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindConsumer(barTapName, module3InputChannel);
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar".getBytes()).setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar").build();
|
||||
moduleOutputChannel.send(message2);
|
||||
|
||||
// other tap still receives messages
|
||||
Message<?> tapped = module2InputChannel.receive(5000);
|
||||
assertNotNull(tapped);
|
||||
|
||||
// Removed tap does not
|
||||
assertNull(module3InputChannel.receive(1000));
|
||||
|
||||
// when other tap stream is deleted
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducers("baz.0");
|
||||
binder.unbindConsumers("baz.0");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(input2Binding);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -256,8 +190,8 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("foo.0", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
@@ -265,8 +199,8 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
Message<?> inbound = moduleInputChannel.receive(5000);
|
||||
assertNotNull(inbound);
|
||||
assertEquals("foo", new String((byte[])inbound.getPayload()));
|
||||
binder.unbindProducers("foo.0");
|
||||
binder.unbindConsumers("foo.0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
// Ignored, since raw mode does not support headers
|
||||
@@ -277,32 +211,29 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
public void testSendAndReceiveWithExplicitConsumerGroup() {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
// Test pub/sub by emulating how StreamPlugin handles taps
|
||||
DirectChannel tapChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
QueueChannel module1InputChannel = new QueueChannel();
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
moduleOutputChannel.addInterceptor(new WireTap(tapChannel));
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel, null);
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubProducer(fooTapName, tapChannel, null);
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null, null);
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel, null);
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null, null);
|
||||
Binding<MessageChannel> input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null);
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
while (!success) {
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = moduleInputChannel.receive(5000);
|
||||
Message<?> inbound = module1InputChannel.receive(5000);
|
||||
assertNotNull(inbound);
|
||||
assertEquals("foo", new String((byte[])inbound.getPayload()));
|
||||
|
||||
@@ -315,11 +246,11 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
assertEquals("foo", new String((byte[])tapped1.getPayload()));
|
||||
assertEquals("foo", new String((byte[])tapped2.getPayload()));
|
||||
assertEquals("foo", new String((byte[]) tapped1.getPayload()));
|
||||
assertEquals("foo", new String((byte[]) tapped2.getPayload()));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindConsumer(barTapName, module3InputChannel);
|
||||
binder.unbind(input3Binding);
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar".getBytes()).build();
|
||||
moduleOutputChannel.send(message2);
|
||||
|
||||
@@ -327,15 +258,18 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
Message<?> tapped = module2InputChannel.receive(5000);
|
||||
assertNotNull(tapped);
|
||||
|
||||
// Removed tap does not
|
||||
// removed tap does not
|
||||
assertNull(module3InputChannel.receive(1000));
|
||||
|
||||
// when other tap stream is deleted
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducers("baz.0");
|
||||
binder.unbindConsumers("baz.0");
|
||||
// re-subscribed tap does receive the message
|
||||
input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null);
|
||||
assertNotNull(module3InputChannel.receive(1000));
|
||||
|
||||
// clean up
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(input2Binding);
|
||||
binder.unbind(input3Binding);
|
||||
binder.unbind(producerBinding);
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -25,7 +25,6 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.binder.BindingCleaner;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -42,7 +41,9 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(RabbitBindingCleaner.class);
|
||||
|
||||
public static final String BINDER_PREFIX = "binder.";
|
||||
private static final String PREFIX_DELIMITER = ".";
|
||||
|
||||
public static final String BINDER_PREFIX = "binder" + PREFIX_DELIMITER;
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> clean(String entity, boolean isJob) {
|
||||
@@ -128,7 +129,7 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
return prefix.substring(0, prefix.length() - 1);
|
||||
}
|
||||
else {
|
||||
return prefix + BinderUtils.GROUP_INDEX_DELIMITER;
|
||||
return prefix + PREFIX_DELIMITER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -62,7 +62,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.AbstractBindingPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
@@ -98,6 +97,7 @@ import com.rabbitmq.client.Envelope;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Jennifer Hickey
|
||||
@@ -150,40 +150,16 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
.addAll(RABBIT_CONSUMER_PROPERTIES)
|
||||
.build();
|
||||
|
||||
private static final Set<Object> SUPPORTED_PUBSUB_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.DURABLE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + concurrency.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_NAMED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + concurrency + partitioning.
|
||||
* Basic + durable + concurrency + partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.DURABLE)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic + concurrency + reply headers + delivery mode (reply).
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_REPLYING_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
// request
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
// reply
|
||||
.add(RabbitPropertiesAccessor.REPLY_HEADER_PATTERNS)
|
||||
.add(RabbitPropertiesAccessor.DELIVERY_MODE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Rabbit producer properties.
|
||||
*/
|
||||
@@ -195,39 +171,15 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
.add(BinderPropertyKeys.COMPRESS)
|
||||
.build();
|
||||
|
||||
private static final Set<Object> SUPPORTED_PUBSUB_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_ADVANCED_PROPERTIES)
|
||||
.build();
|
||||
|
||||
private static final Set<Object> SUPPORTED_NAMED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_ADVANCED_PROPERTIES)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Partitioning + rabbit producer properties.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.DIRECT_BINDING_ALLOWED)
|
||||
.addAll(PRODUCER_BATCHING_BASIC_PROPERTIES)
|
||||
.addAll(PRODUCER_BATCHING_ADVANCED_PROPERTIES)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Basic producer + basic consumer + concurrency + reply headers.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_REQUESTING_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
// request
|
||||
.addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES)
|
||||
// reply
|
||||
.addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(RabbitPropertiesAccessor.REPLY_HEADER_PATTERNS)
|
||||
.add(RabbitPropertiesAccessor.AUTO_BIND_DLQ)
|
||||
.build();
|
||||
|
||||
private static final MessagePropertiesConverter inboundMessagePropertiesConverter =
|
||||
@@ -436,51 +388,27 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(final String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) {
|
||||
String baseQueueName = groupedName(name, group);
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("declaring queue for inbound: " + name);
|
||||
}
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_NAMED_CONSUMER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name);
|
||||
}
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String queueName = applyPrefix(accessor.getPrefix(this.defaultPrefix), name);
|
||||
TopicExchange exchange = new TopicExchange(queueName);
|
||||
declareExchange(queueName, exchange);
|
||||
validateConsumerProperties(baseQueueName, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
String prefix = accessor.getPrefix(this.defaultPrefix);
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
|
||||
String queueName = applyPrefix(prefix, baseQueueName);
|
||||
int partitionIndex = accessor.getPartitionIndex();
|
||||
String dlqNameRoot = name;
|
||||
if (partitionIndex >= 0) {
|
||||
String partitionSuffix = "-" + partitionIndex;
|
||||
queueName += partitionSuffix;
|
||||
dlqNameRoot += partitionSuffix;
|
||||
}
|
||||
Queue queue = new Queue(queueName, true, false, false, queueArgs(accessor, queueName));
|
||||
declareQueue(queueName, queue);
|
||||
autoBindDLQ(dlqNameRoot, accessor);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(queueName);
|
||||
declareBinding(queueName, binding);
|
||||
doRegisterConsumer(name, moduleInputChannel, queue, accessor, false);
|
||||
bindExistingProducerDirectlyIfPossible(name, moduleInputChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String exchangeName, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
String name = BinderUtils.groupedName(exchangeName, group);
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("declaring pubsub for inbound: " + name + ", bound to: " + exchangeName);
|
||||
}
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
validateConsumerProperties(name, properties, SUPPORTED_PUBSUB_CONSUMER_PROPERTIES);
|
||||
String prefix = accessor.getPrefix(this.defaultPrefix);
|
||||
TopicExchange exchange = new TopicExchange(applyPrefix(prefix, exchangeName));
|
||||
declareExchange(exchange.getName(), exchange);
|
||||
Queue queue;
|
||||
boolean durable = accessor.isDurable(this.defaultDurableSubscription);
|
||||
String queueName = applyPrefix(prefix, name);
|
||||
if (durable) {
|
||||
queue = new Queue(queueName, true, false, false, queueArgs(accessor, queueName));
|
||||
}
|
||||
@@ -488,11 +416,18 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
queue = new Queue(queueName, false, false, true);
|
||||
}
|
||||
declareQueue(queueName, queue);
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
|
||||
doRegisterConsumer(name, moduleInputChannel, queue, accessor, true);
|
||||
if (durable) {
|
||||
autoBindDLQ(name, accessor);
|
||||
if (partitionIndex >= 0) {
|
||||
String bindingKey = String.format("%s-%d", name, partitionIndex);
|
||||
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);
|
||||
if (durable) {
|
||||
autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, accessor);
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
private Map<String, Object> queueArgs(RabbitPropertiesAccessor accessor, String queueName) {
|
||||
@@ -504,8 +439,9 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
return args;
|
||||
}
|
||||
|
||||
private void doRegisterConsumer(String name, MessageChannel moduleInputChannel, Queue queue,
|
||||
RabbitPropertiesAccessor properties, boolean isPubSub) {
|
||||
private Binding<MessageChannel> doRegisterConsumer(String name, String group, MessageChannel moduleInputChannel, Queue queue,
|
||||
RabbitPropertiesAccessor properties) {
|
||||
Binding<MessageChannel> consumerBinding = null;
|
||||
// Fix for XD-2503
|
||||
// Temporarily overrides the thread context classloader with the one where the SimpleMessageListenerContainer
|
||||
// is defined
|
||||
@@ -520,15 +456,15 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
listenerContainer.setChannelTransacted(properties.getTransacted(this.defaultChannelTransacted));
|
||||
listenerContainer.setDefaultRequeueRejected(properties.getRequeueRejected(this
|
||||
.defaultDefaultRequeueRejected));
|
||||
if (!isPubSub) {
|
||||
int concurrency = properties.getConcurrency(this.defaultConcurrency);
|
||||
concurrency = concurrency > 0 ? concurrency : 1;
|
||||
listenerContainer.setConcurrentConsumers(concurrency);
|
||||
int maxConcurrency = properties.getMaxConcurrency(this.defaultMaxConcurrency);
|
||||
if (maxConcurrency > concurrency) {
|
||||
listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
|
||||
}
|
||||
|
||||
int concurrency = properties.getConcurrency(this.defaultConcurrency);
|
||||
concurrency = concurrency > 0 ? concurrency : 1;
|
||||
listenerContainer.setConcurrentConsumers(concurrency);
|
||||
int maxConcurrency = properties.getMaxConcurrency(this.defaultMaxConcurrency);
|
||||
if (maxConcurrency > concurrency) {
|
||||
listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
|
||||
}
|
||||
|
||||
listenerContainer.setPrefetchCount(properties.getPrefetchCount(this.defaultPrefetchCount));
|
||||
listenerContainer.setTxSize(properties.getTxSize(this.defaultTxSize));
|
||||
listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
|
||||
@@ -559,7 +495,7 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
mapper.setReplyHeaderNames(properties.getReplyHeaderPattens(this.defaultReplyHeaderPatterns));
|
||||
adapter.setHeaderMapper(mapper);
|
||||
adapter.afterPropertiesSet();
|
||||
Binding consumerBinding = Binding.forConsumer(name, adapter, moduleInputChannel, properties);
|
||||
consumerBinding = Binding.forConsumer(name, group, adapter, moduleInputChannel, properties);
|
||||
addBinding(consumerBinding);
|
||||
ReceivingHandler convertingBridge = new ReceivingHandler();
|
||||
convertingBridge.setOutputChannel(moduleInputChannel);
|
||||
@@ -571,6 +507,7 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(originalClassloader);
|
||||
}
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
private MessageRecoverer determineRecoverer(String name, RabbitPropertiesAccessor properties) {
|
||||
@@ -587,54 +524,36 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_NAMED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
if (!bindNewProducerDirectlyIfPossible(name, (SubscribableChannel) moduleOutputChannel, accessor)) {
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("declaring queue for outbound: " + name);
|
||||
}
|
||||
AmqpOutboundEndpoint queue = this.buildOutboundEndpoint(name, accessor, determineRabbitTemplate(accessor));
|
||||
doRegisterProducer(name, moduleOutputChannel, queue, accessor);
|
||||
}
|
||||
}
|
||||
|
||||
private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitPropertiesAccessor properties,
|
||||
RabbitTemplate rabbitTemplate) {
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
String queueName = applyPrefix(prefix, name);
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
String partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = properties.getPartitionKeyExpression();
|
||||
TopicExchange exchange = new TopicExchange(queueName);
|
||||
declareExchange(queueName, exchange);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
|
||||
endpoint.setExchangeName(exchange.getName());
|
||||
String baseQueueName = exchangeName + ".default";
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
Queue queue = new Queue(queueName, true, false, false, queueArgs(properties, queueName));
|
||||
declareQueue(name, queue);
|
||||
autoBindDLQ(name, properties);
|
||||
endpoint.setRoutingKey(queueName);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(queueName);
|
||||
declareBinding(queueName, binding);
|
||||
Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(properties, baseQueueName));
|
||||
declareQueue(baseQueueName, queue);
|
||||
autoBindDLQ(baseQueueName, baseQueueName, properties);
|
||||
endpoint.setRoutingKey(name);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(name);
|
||||
declareBinding(baseQueueName, binding);
|
||||
}
|
||||
else {
|
||||
endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression
|
||||
(queueName)));
|
||||
// if the stream is partitioned, create one queue for each target partition
|
||||
endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name)));
|
||||
// if the stream is partitioned, create one queue for each target partition for the default group
|
||||
for (int i = 0; i < properties.getNextModuleCount(); i++) {
|
||||
String partitionSuffix = "-" + i;
|
||||
Queue queue = new Queue(queueName + partitionSuffix, true, false, false, queueArgs(properties, queueName));
|
||||
String partitionQueueName = baseQueueName + partitionSuffix;
|
||||
Queue queue = new Queue(partitionQueueName, true, false, false,
|
||||
queueArgs(properties, partitionQueueName));
|
||||
declareQueue(queue.getName(), queue);
|
||||
autoBindDLQ(name + partitionSuffix, properties);
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(queue.getName()));
|
||||
autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties);
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(name + partitionSuffix));
|
||||
}
|
||||
}
|
||||
configureOutboundHandler(endpoint, properties);
|
||||
@@ -652,18 +571,14 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PUBSUB_PRODUCER_PROPERTIES);
|
||||
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);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(determineRabbitTemplate(accessor));
|
||||
endpoint.setExchangeName(exchangeName);
|
||||
endpoint.setRoutingKey(name);
|
||||
configureOutboundHandler(endpoint, accessor);
|
||||
doRegisterProducer(name, moduleOutputChannel, endpoint, accessor);
|
||||
AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, accessor, determineRabbitTemplate(accessor));
|
||||
return doRegisterProducer(name, outputChannel, endpoint, accessor);
|
||||
}
|
||||
|
||||
private RabbitTemplate determineRabbitTemplate(RabbitPropertiesAccessor properties) {
|
||||
@@ -691,12 +606,12 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
private void doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, RabbitPropertiesAccessor properties) {
|
||||
this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
|
||||
return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
|
||||
}
|
||||
|
||||
private void doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, String replyTo, RabbitPropertiesAccessor properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
MessageHandler handler = new SendingHandler(delegate, replyTo, properties);
|
||||
@@ -704,72 +619,33 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
consumer.setBeanFactory(getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
Binding producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer, properties);
|
||||
Binding<MessageChannel> producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer, properties);
|
||||
addBinding(producerBinding);
|
||||
producerBinding.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("binding requestor: " + name);
|
||||
}
|
||||
validateProducerProperties(name, properties, SUPPORTED_REQUESTING_PRODUCER_PROPERTIES);
|
||||
Assert.isInstanceOf(SubscribableChannel.class, requests);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String queueName = applyRequests(name);
|
||||
AmqpOutboundEndpoint queue = this.buildOutboundEndpoint(queueName, accessor, this.rabbitTemplate);
|
||||
queue.setBeanFactory(this.getBeanFactory());
|
||||
|
||||
String replyQueueName = accessor.getPrefix(this.defaultPrefix) + name + ".replies."
|
||||
+ this.getIdGenerator().generateId();
|
||||
this.doRegisterProducer(name, requests, queue, replyQueueName, accessor);
|
||||
Queue replyQueue = new Queue(replyQueueName, false, false, true); // auto-delete
|
||||
declareQueue(replyQueueName, replyQueue);
|
||||
this.doRegisterConsumer(name, replies, replyQueue, accessor, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("binding replier: " + name);
|
||||
}
|
||||
validateConsumerProperties(name, properties, SUPPORTED_REPLYING_CONSUMER_PROPERTIES);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
Queue requestQueue = new Queue(applyPrefix(accessor.getPrefix(this.defaultPrefix), applyRequests(name)));
|
||||
declareQueue(requestQueue.getName(), requestQueue);
|
||||
this.doRegisterConsumer(name, requests, requestQueue, accessor, false);
|
||||
|
||||
AmqpOutboundEndpoint replyQueue = new AmqpOutboundEndpoint(this.rabbitTemplate);
|
||||
replyQueue.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression("headers['" + AmqpHeaders.REPLY_TO +
|
||||
"']"));
|
||||
configureOutboundHandler(replyQueue, accessor);
|
||||
doRegisterProducer(name, replies, replyQueue, accessor);
|
||||
return producerBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* If so requested, declare the DLX/DLQ and bind it. The DLQ is bound to the DLX with a routing key of the original
|
||||
* queue name because we use default exchange routing by queue name for the original message.
|
||||
* @param name The name.
|
||||
* @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.
|
||||
*/
|
||||
private void autoBindDLQ(final String name, RabbitPropertiesAccessor properties) {
|
||||
private void autoBindDLQ(final String queueName, String routingKey, RabbitPropertiesAccessor properties) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("autoBindDLQ=" + properties.getAutoBindDLQ(this.defaultAutoBindDLQ)
|
||||
+ " for: " + name);
|
||||
+ " for: " + queueName);
|
||||
}
|
||||
if (properties.getAutoBindDLQ(this.defaultAutoBindDLQ)) {
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
String queueName = applyPrefix(prefix, name);
|
||||
String dlqName = constructDLQName(queueName);
|
||||
Queue dlq = new Queue(dlqName);
|
||||
declareQueue(dlqName, dlq);
|
||||
final String dlxName = deadLetterExchangeName(prefix);
|
||||
final DirectExchange dlx = new DirectExchange(dlxName);
|
||||
declareExchange(dlxName, dlx);
|
||||
declareBinding(dlqName, BindingBuilder.bind(dlq).to(dlx).with(queueName));
|
||||
declareBinding(dlqName, BindingBuilder.bind(dlq).to(dlx).with(routingKey));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,21 +690,10 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, MessageChannel channel) {
|
||||
super.unbindConsumer(name, channel);
|
||||
cleanAutoDeclareContext(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
super.unbindConsumers(name);
|
||||
cleanAutoDeclareContext(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
super.unbindPubSubConsumers(name, group);
|
||||
cleanAutoDeclareContext(BinderUtils.groupedName(name, group));
|
||||
protected void afterUnbind(Binding<MessageChannel> binding) {
|
||||
if (Binding.Type.consumer.equals(binding.getType())) {
|
||||
cleanAutoDeclareContext(groupedName(binding.getName(), binding.getGroup()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addToAutoDeclareContext(String name, Object bean) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -46,7 +46,7 @@ public class LocalizedQueueConnectionFactoryIntegrationTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
ConnectionFactory defaultConnectionFactory = new CachingConnectionFactory("localhost");
|
||||
ConnectionFactory defaultConnectionFactory = rabbitAvailableRule.getResource();
|
||||
String[] addresses = new String[] { "localhost:9999", "localhost:5672" };
|
||||
String[] adminAddresses = new String[] { "http://localhost:15672", "http://localhost:15672" };
|
||||
String[] nodes = new String[] { "foo@bar", "rabbit@localhost" };
|
||||
@@ -66,6 +66,8 @@ public class LocalizedQueueConnectionFactoryIntegrationTests {
|
||||
RabbitTemplate template = new RabbitTemplate(targetConnectionFactory);
|
||||
template.convertAndSend("", queue.getName(), "foo");
|
||||
assertEquals("foo", template.receiveAndConvert(queue.getName()));
|
||||
((CachingConnectionFactory) targetConnectionFactory).destroy();
|
||||
admin.deleteQueue(queue.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -69,8 +69,8 @@ public class RabbitBinderCleanerTests {
|
||||
CachingConnectionFactory connectionFactory = rabbitWithMgmtEnabled.getResource();
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
String queue1Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + "." + i);
|
||||
String queue2Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream2 + "." + i);
|
||||
String queue1Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + ".default." + i);
|
||||
String queue2Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream2 + ".default." + i);
|
||||
if (firstQueue == null) {
|
||||
firstQueue = queue1Name;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class RabbitBinderCleanerTests {
|
||||
|
||||
@Override
|
||||
public Void doInRabbit(Channel channel) throws Exception {
|
||||
String queueName = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + "." + 4);
|
||||
String queueName = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + ".default." + 4);
|
||||
String consumerTag = channel.basicConsume(queueName, new DefaultConsumer(channel));
|
||||
try {
|
||||
waitForConsumerStateNot(queueName, 0);
|
||||
@@ -160,8 +160,8 @@ public class RabbitBinderCleanerTests {
|
||||
// should *not* clean stream2
|
||||
assertEquals(10, cleanedQueues.size());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertEquals(BINDER_PREFIX + stream1 + "." + i, cleanedQueues.get(i * 2));
|
||||
assertEquals(BINDER_PREFIX + stream1 + "." + i + ".dlq", cleanedQueues.get(i * 2 + 1));
|
||||
assertEquals(BINDER_PREFIX + stream1 + ".default." + i, cleanedQueues.get(i * 2));
|
||||
assertEquals(BINDER_PREFIX + stream1 + ".default." + i + ".dlq", cleanedQueues.get(i * 2 + 1));
|
||||
}
|
||||
List<String> cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertEquals(6, cleanedExchanges.size());
|
||||
@@ -172,7 +172,7 @@ public class RabbitBinderCleanerTests {
|
||||
cleanedQueues = cleanedMap.get("queues");
|
||||
assertEquals(5, cleanedQueues.size());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertEquals(BINDER_PREFIX + stream2 + "." + i, cleanedQueues.get(i));
|
||||
assertEquals(BINDER_PREFIX + stream2 + ".default." + i, cleanedQueues.get(i));
|
||||
}
|
||||
cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertEquals(6, cleanedExchanges.size());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.rabbit;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
@@ -26,8 +25,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -58,14 +55,13 @@ 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.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
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.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -110,8 +106,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
binder.bindProducer("bad.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("bad.0", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bad.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel, null);
|
||||
Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
@@ -125,8 +121,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
});
|
||||
moduleOutputChannel.send(message);
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
binder.unbindConsumers("bad.0");
|
||||
binder.unbindProducers("bad.0");
|
||||
binder.unbind(consumerBinding);
|
||||
binder.unbind(producerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,15 +130,15 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("transacted", "true"); // test transacted with defaults; not allowed with ackmode NONE
|
||||
binder.bindConsumer("props.0", new DirectChannel(), properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", null, new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
assertEquals(AcknowledgeMode.AUTO, container.getAcknowledgeMode());
|
||||
assertEquals(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "props.0", container.getQueueNames()[0]);
|
||||
assertEquals(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "props.0.default", container.getQueueNames()[0]);
|
||||
assertTrue(TestUtils.getPropertyValue(container, "transactional", Boolean.class));
|
||||
assertEquals(1, TestUtils.getPropertyValue(container, "concurrentConsumers"));
|
||||
assertNull(TestUtils.getPropertyValue(container, "maxConcurrentConsumers"));
|
||||
@@ -154,7 +150,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertEquals(1000L, TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.initialInterval"));
|
||||
assertEquals(10000L, TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.maxInterval"));
|
||||
assertEquals(2.0, TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.multiplier"));
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbind(consumerBinding);
|
||||
assertEquals(0, bindings.size());
|
||||
|
||||
properties = new Properties();
|
||||
@@ -171,46 +167,26 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
properties.put("requeue", "false");
|
||||
properties.put("txSize", "10");
|
||||
properties.put("partitionIndex", 0);
|
||||
binder.bindConsumer("props.0", new DirectChannel(), properties);
|
||||
consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindingsNow = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindingsNow = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindingsNow.size());
|
||||
endpoint = bindingsNow.get(0).getEndpoint();
|
||||
container = verifyContainer(endpoint);
|
||||
|
||||
assertEquals("foo.props.0", container.getQueueNames()[0]);
|
||||
assertEquals("foo.props.0.test", container.getQueueNames()[0]);
|
||||
|
||||
try {
|
||||
binder.bindPubSubConsumer("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support consumer properties: "),
|
||||
containsString("partitionIndex"),
|
||||
containsString("concurrency"),
|
||||
containsString(" for dummy.")));
|
||||
}
|
||||
try {
|
||||
binder.bindConsumer("queue:dummy", null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertEquals(getClassUnderTestName() + " does not support consumer property: partitionIndex for queue:dummy.",
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbind(consumerBinding);
|
||||
assertEquals(0, bindingsNow.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
@@ -219,7 +195,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
List<?> requestHeaders = TestUtils.getPropertyValue(endpoint,
|
||||
"handler.delegate.headerMapper.requestHeaderMatcher.strategies", List.class);
|
||||
assertEquals(2, requestHeaders.size());
|
||||
binder.unbindProducers("props.0");
|
||||
binder.unbind(producerBinding);
|
||||
assertEquals(0, bindings.size());
|
||||
|
||||
Properties properties = new Properties();
|
||||
@@ -232,11 +208,11 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
|
||||
binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
assertEquals(1, bindings.size());
|
||||
endpoint = bindings.get(0).getEndpoint();
|
||||
assertEquals(
|
||||
"'foo.props.0-' + headers['partition']",
|
||||
"'props.0-' + headers['partition']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
SpelExpression.class).getExpressionString());
|
||||
mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
@@ -244,167 +220,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertEquals(MessageDeliveryMode.NON_PERSISTENT, mode);
|
||||
verifyFooRequestProducer(endpoint);
|
||||
|
||||
try {
|
||||
binder.bindPubSubProducer("dummy", new DirectChannel(), properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), containsString("for dummy."));
|
||||
}
|
||||
try {
|
||||
binder.bindProducer("queue:dummy", new DirectChannel(), properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), containsString("for queue:dummy."));
|
||||
}
|
||||
|
||||
binder.unbindProducers("props.0");
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestReplyRequestorProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
|
||||
properties.put("requestHeaderPatterns", "foo");
|
||||
properties.put("replyHeaderPatterns", "bar");
|
||||
|
||||
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("requeue", "false");
|
||||
properties.put("txSize", "10");
|
||||
|
||||
binder.bindRequestor("props.0", new DirectChannel(), new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
|
||||
assertEquals(2, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint(); // producer
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
assertEquals(MessageDeliveryMode.NON_PERSISTENT, mode);
|
||||
verifyFooRequestBarReplyProducer(endpoint);
|
||||
|
||||
endpoint = bindings.get(1).getEndpoint(); // consumer
|
||||
|
||||
verifyContainer(endpoint);
|
||||
|
||||
verifyBarReplyConsumer(endpoint);
|
||||
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
properties.put("partitionIndex", "0");
|
||||
try {
|
||||
binder.bindRequestor("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), allOf(containsString("partitionIndex"), containsString("for dummy.")));
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbindProducers("props.0");
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestReplyReplierProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
|
||||
properties.put("requestHeaderPatterns", "foo");
|
||||
properties.put("replyHeaderPatterns", "bar");
|
||||
|
||||
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("requeue", "false");
|
||||
properties.put("txSize", "10");
|
||||
|
||||
binder.bindReplier("props.0", new DirectChannel(), new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
|
||||
assertEquals(2, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(1).getEndpoint(); // producer
|
||||
assertEquals(
|
||||
"headers['amqp_replyTo']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
SpelExpression.class).getExpressionString());
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
assertEquals(MessageDeliveryMode.NON_PERSISTENT, mode);
|
||||
|
||||
verifyFooRequestBarReplyProducer(endpoint);
|
||||
|
||||
endpoint = bindings.get(0).getEndpoint(); // consumer
|
||||
|
||||
verifyContainer(endpoint);
|
||||
|
||||
verifyBarReplyConsumer(endpoint);
|
||||
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
properties.put("partitionIndex", "0");
|
||||
try {
|
||||
binder.bindReplier("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support consumer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), allOf(containsString("partitionIndex"), containsString("for dummy.")));
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbindProducers("props.0");
|
||||
binder.unbind(producerBinding);
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@@ -430,14 +246,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindPubSubConsumer("durabletest.0", moduleInputChannel, "tgroup", properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("durabletest.0", "tgroup", moduleInputChannel, properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend(TEST_PREFIX + "durabletest.0", "", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "tgroup.durabletest.0.dlq");
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "durabletest.0.tgroup.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertEquals("foo", deadLetter);
|
||||
break;
|
||||
@@ -446,13 +262,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
assertTrue(n < 100);
|
||||
|
||||
binder.unbindConsumer("durabletest.0", moduleInputChannel);
|
||||
binder.unbindPubSubConsumers("durabletest.0", "tgroup");
|
||||
assertNotNull(admin.getQueueProperties(TEST_PREFIX + "tgroup.durabletest.0.dlq"));
|
||||
admin.deleteQueue(TEST_PREFIX + "tgroup.durabletest.0.dlq");
|
||||
admin.deleteQueue(TEST_PREFIX + "tgroup.durabletest.0");
|
||||
admin.deleteExchange(TEST_PREFIX + "durabletest.0");
|
||||
admin.deleteExchange(TEST_PREFIX + "DLX");
|
||||
binder.unbind(consumerBinding);
|
||||
assertNotNull(admin.getQueueProperties(TEST_PREFIX + "durabletest.0.tgroup.dlq"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -476,18 +287,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindPubSubConsumer("nondurabletest.0", moduleInputChannel, "tgroup", properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("nondurabletest.0", "tgroup", moduleInputChannel, properties);
|
||||
|
||||
binder.unbindPubSubConsumers("nondurabletest.0", "tgroup");
|
||||
binder.unbind(consumerBinding);
|
||||
assertNull(admin.getQueueProperties(TEST_PREFIX + "nondurabletest.0.dlq"));
|
||||
admin.deleteQueue(TEST_PREFIX + "tgroup.nondurabletest.0");
|
||||
admin.deleteExchange(TEST_PREFIX + "nondurabletest.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
@@ -504,14 +311,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindConsumer("dlqtest", moduleInputChannel, properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("dlqtest", null, moduleInputChannel, properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqtest", "foo");
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqtest.default", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "dlqtest.dlq");
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "dlqtest.default.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertEquals("foo", deadLetter);
|
||||
break;
|
||||
@@ -520,10 +327,176 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
assertTrue(n < 100);
|
||||
|
||||
binder.unbindConsumer("dlqtest", moduleInputChannel);
|
||||
admin.deleteQueue(TEST_PREFIX + "dlqtest.dlq");
|
||||
admin.deleteQueue(TEST_PREFIX + "dlqtest");
|
||||
admin.deleteExchange(TEST_PREFIX + "DLX");
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@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");
|
||||
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", null, new QueueChannel(), properties);
|
||||
properties.put("partitionIndex", "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", null, 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");
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.0", output, properties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch0.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch0.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
input1.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch1.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch1.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
assertTrue(latch0.await(10, TimeUnit.SECONDS));
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.setReceiveTimeout(10000);
|
||||
|
||||
String streamDLQName = "bindertest.partDLQ.0.dlqPartGrp.dlq";
|
||||
|
||||
org.springframework.amqp.core.Message received = template.receive(streamDLQName);
|
||||
assertNotNull(received);
|
||||
assertEquals(1, received.getMessageProperties().getHeaders().get("partition"));
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
received = template.receive(streamDLQName);
|
||||
assertNotNull(received);
|
||||
assertEquals(0, received.getMessageProperties().getHeaders().get("partition"));
|
||||
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(defaultConsumerBinding1);
|
||||
binder.unbind(defaultConsumerBinding2);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQPartionedProducerFirst() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
|
||||
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");
|
||||
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");
|
||||
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", null, new QueueChannel(), properties);
|
||||
properties.put("partitionIndex", "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", null, new QueueChannel(), properties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch0.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch0.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
input1.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch1.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch1.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
assertTrue(latch1.await(10, TimeUnit.SECONDS));
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
assertTrue(latch0.await(10, TimeUnit.SECONDS));
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.setReceiveTimeout(10000);
|
||||
|
||||
String streamDLQName = "bindertest.partDLQ.1.dlqPartGrp.dlq";
|
||||
|
||||
org.springframework.amqp.core.Message received = template.receive(streamDLQName);
|
||||
assertNotNull(received);
|
||||
assertEquals(1, received.getMessageProperties().getHeaders().get("partition"));
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
received = template.receive(streamDLQName);
|
||||
assertNotNull(received);
|
||||
assertEquals(0, received.getMessageProperties().getHeaders().get("partition"));
|
||||
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(defaultConsumerBinding1);
|
||||
binder.unbind(defaultConsumerBinding2);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -532,8 +505,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("x-dead-letter-exchange", TEST_PREFIX + "DLX");
|
||||
args.put("x-dead-letter-routing-key", TEST_PREFIX + "dlqpubtest");
|
||||
Queue queue = new Queue(TEST_PREFIX + "dlqpubtest", true, false, false, args);
|
||||
args.put("x-dead-letter-routing-key", TEST_PREFIX + "dlqpubtest.default");
|
||||
Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args);
|
||||
admin.declareQueue(queue);
|
||||
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
@@ -553,14 +526,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindConsumer("dlqpubtest", moduleInputChannel, properties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("dlqpubtest", "default", moduleInputChannel, properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqpubtest", "foo");
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqpubtest.default", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
org.springframework.amqp.core.Message deadLetter = template.receive(TEST_PREFIX + "dlqpubtest.dlq");
|
||||
org.springframework.amqp.core.Message deadLetter = template.receive(TEST_PREFIX + "dlqpubtest.default.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertEquals("foo", new String(deadLetter.getBody()));
|
||||
assertNotNull(deadLetter.getMessageProperties().getHeaders().get("x-exception-stacktrace"));
|
||||
@@ -570,10 +543,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
assertTrue(n < 100);
|
||||
|
||||
binder.unbindConsumer("dlqpubtest", moduleInputChannel);
|
||||
admin.deleteQueue(TEST_PREFIX + "dlqpubtest.dlq");
|
||||
admin.deleteQueue(TEST_PREFIX + "dlqpubtest");
|
||||
admin.deleteExchange(TEST_PREFIX + "DLX");
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -591,9 +561,9 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("batchingProducer");
|
||||
binder.bindProducer("batching.0", output, properties);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("batching.0", output, properties);
|
||||
|
||||
while (template.receive(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "batching.0") != null) {
|
||||
while (template.receive(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "batching.0.default") != null) {
|
||||
}
|
||||
|
||||
Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class));
|
||||
@@ -606,7 +576,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
output.send(new GenericMessage<>("foo".getBytes()));
|
||||
output.send(new GenericMessage<>("bar".getBytes()));
|
||||
|
||||
Object out = spyOn("batching.0").receive(false);
|
||||
Object out = spyOn("batching.0.default").receive(false);
|
||||
assertThat(out, instanceOf(byte[].class));
|
||||
assertEquals("\u0000\u0000\u0000\u0003foo\u0000\u0000\u0000\u0003bar", new String((byte[]) out));
|
||||
|
||||
@@ -616,7 +586,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
QueueChannel input = new QueueChannel();
|
||||
input.setBeanName("batchingConsumer");
|
||||
binder.bindConsumer("batching.0", input, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("batching.0", "test", input, null);
|
||||
|
||||
output.send(new GenericMessage<>("foo".getBytes()));
|
||||
output.send(new GenericMessage<>("bar".getBytes()));
|
||||
@@ -629,8 +599,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertEquals("bar", new String(in.getPayload()));
|
||||
assertNull(in.getHeaders().get(AmqpHeaders.DELIVERY_MODE));
|
||||
|
||||
binder.unbindProducers("batching.0");
|
||||
binder.unbindConsumers("batching.0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -641,53 +611,51 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public void testLateBinding() throws Exception {
|
||||
RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy();
|
||||
CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort());
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(cf);
|
||||
AbstractApplicationContext applicationContext = mock(AbstractApplicationContext.class);
|
||||
when(applicationContext.getBeanFactory()).thenReturn(mock(ConfigurableListableBeanFactory.class));
|
||||
binder.setApplicationContext(applicationContext);
|
||||
binder.setDefaultAutoBindDLQ(true);
|
||||
binder.afterPropertiesSet();
|
||||
RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf);
|
||||
rabbitBinder.setDefaultAutoBindDLQ(true);
|
||||
AbstractTestBinder<RabbitMessageChannelBinder> binder = new RabbitTestBinder(cf, rabbitBinder);
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "latebinder.");
|
||||
|
||||
MessageChannel moduleOutputChannel = new DirectChannel();
|
||||
binder.bindProducer("late.0", moduleOutputChannel, properties);
|
||||
Binding<MessageChannel> late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel, properties);
|
||||
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindConsumer("late.0", moduleInputChannel, properties);
|
||||
Binding<MessageChannel> late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel, properties);
|
||||
|
||||
properties.put("partitionKeyExpression", "payload.equals('0') ? 0 : 1");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
properties.put("nextModuleCount", "2");
|
||||
|
||||
MessageChannel partOutputChannel = new DirectChannel();
|
||||
binder.bindProducer("partlate.0", partOutputChannel, properties);
|
||||
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");
|
||||
binder.bindConsumer("partlate.0", partInputChannel0, properties);
|
||||
Binding<MessageChannel> partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
binder.bindConsumer("partlate.0", partInputChannel1, properties);
|
||||
Binding<MessageChannel> partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1, properties);
|
||||
|
||||
binder.setDefaultAutoBindDLQ(false);
|
||||
rabbitBinder.setDefaultAutoBindDLQ(false);
|
||||
properties.clear();
|
||||
properties.put("prefix", "latebinder.");
|
||||
MessageChannel noDLQOutputChannel = new DirectChannel();
|
||||
binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, properties);
|
||||
Binding<MessageChannel> noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, properties);
|
||||
|
||||
QueueChannel noDLQInputChannel = new QueueChannel();
|
||||
binder.bindConsumer("lateNoDLQ.0", noDLQInputChannel, properties);
|
||||
Binding<MessageChannel> noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel, properties);
|
||||
|
||||
MessageChannel pubSubOutputChannel = new DirectChannel();
|
||||
binder.bindPubSubProducer("latePubSub", pubSubOutputChannel, properties);
|
||||
MessageChannel outputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel, properties);
|
||||
QueueChannel pubSubInputChannel = new QueueChannel();
|
||||
binder.bindPubSubConsumer("latePubSub", pubSubInputChannel, "lategroup", properties);
|
||||
Binding<MessageChannel> nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup", pubSubInputChannel, properties);
|
||||
QueueChannel durablePubSubInputChannel = new QueueChannel();
|
||||
properties.setProperty("durableSubscription", "true");
|
||||
binder.bindPubSubConsumer("latePubSub", durablePubSubInputChannel, "lateDurableGroup", properties);
|
||||
Binding<MessageChannel> durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup", durablePubSubInputChannel, properties);
|
||||
|
||||
proxy.start();
|
||||
|
||||
@@ -701,7 +669,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertNotNull(message);
|
||||
assertEquals("bar", message.getPayload());
|
||||
|
||||
pubSubOutputChannel.send(new GenericMessage<>("baz"));
|
||||
outputChannel.send(new GenericMessage<>("baz"));
|
||||
message = pubSubInputChannel.receive(10000);
|
||||
assertNotNull(message);
|
||||
assertEquals("baz", message.getPayload());
|
||||
@@ -718,28 +686,22 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertNotNull(message);
|
||||
assertEquals("1", message.getPayload());
|
||||
|
||||
binder.unbindProducer("late.0", moduleOutputChannel);
|
||||
binder.unbindConsumer("late.0", moduleInputChannel);
|
||||
binder.unbindProducer("partlate.0", moduleOutputChannel);
|
||||
binder.unbindConsumers("partlate.0");
|
||||
binder.unbind(late0ProducerBinding);
|
||||
binder.unbind(late0ConsumerBinding);
|
||||
binder.unbind(partlate0ProducerBinding);
|
||||
binder.unbind(partlate0Consumer0Binding);
|
||||
binder.unbind(partlate0Consumer1Binding);
|
||||
binder.unbind(noDlqProducerBinding);
|
||||
binder.unbind(noDlqConsumerBinding);
|
||||
binder.unbind(pubSubProducerBinding);
|
||||
binder.unbind(nonDurableConsumerBinding);
|
||||
binder.unbind(durableConsumerBinding);
|
||||
|
||||
binder.cleanup();
|
||||
|
||||
proxy.stop();
|
||||
cf.destroy();
|
||||
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
admin.deleteQueue("latebinder.late.0");
|
||||
admin.deleteQueue("latebinder.lateNoDLQ.0");
|
||||
admin.deleteQueue("latebinder.partlate.0-0");
|
||||
admin.deleteQueue("latebinder.partlate.0-1");
|
||||
admin.deleteQueue("latebinder.late.0.dlq");
|
||||
admin.deleteQueue("latebinder.partlate.0-0.dlq");
|
||||
admin.deleteQueue("latebinder.partlate.0-1.dlq");
|
||||
admin.deleteQueue("latebinder.lateDurableGroup.latePubSub");
|
||||
admin.deleteExchange("latebinder.late.0");
|
||||
admin.deleteExchange("latebinder.lateNoDLQ.0");
|
||||
admin.deleteExchange("latebinder.partlate.0");
|
||||
admin.deleteExchange("latebinder.latePubSub");
|
||||
admin.deleteExchange("latebinder.DLX");
|
||||
this.rabbitAvailableRule.getResource().destroy();
|
||||
}
|
||||
|
||||
@@ -772,26 +734,6 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
return container;
|
||||
}
|
||||
|
||||
private void verifyBarReplyConsumer(AbstractEndpoint endpoint) {
|
||||
List<?> replyMatchers;
|
||||
replyMatchers = TestUtils.getPropertyValue(endpoint,
|
||||
"headerMapper.replyHeaderMatcher.strategies",
|
||||
List.class);
|
||||
assertEquals(1, replyMatchers.size());
|
||||
assertEquals("bar",
|
||||
TestUtils.getPropertyValue(replyMatchers.get(0), "patterns", Collection.class).iterator().next());
|
||||
}
|
||||
|
||||
private void verifyFooRequestBarReplyProducer(AbstractEndpoint endpoint) {
|
||||
verifyFooRequestProducer(endpoint);
|
||||
List<?> replyMatchers = TestUtils.getPropertyValue(endpoint,
|
||||
"handler.delegate.headerMapper.replyHeaderMatcher.strategies",
|
||||
List.class);
|
||||
assertEquals(1, replyMatchers.size());
|
||||
assertEquals("bar",
|
||||
TestUtils.getPropertyValue(replyMatchers.get(0), "patterns", Collection.class).iterator().next());
|
||||
}
|
||||
|
||||
private void verifyFooRequestProducer(AbstractEndpoint endpoint) {
|
||||
List<?> requestMatchers = TestUtils.getPropertyValue(endpoint,
|
||||
"handler.delegate.headerMapper.requestHeaderMatcher.strategies",
|
||||
@@ -807,6 +749,11 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
SpelExpression.class).getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExpectedRoutingBaseDestination(String name, String group) {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.exchangeNameExpression",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -23,19 +23,20 @@ import java.util.Set;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
|
||||
/**
|
||||
* Test support class for {@link RabbitMessageChannelBinder}.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBinder> {
|
||||
|
||||
@@ -45,8 +46,15 @@ public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBin
|
||||
|
||||
private final Set<String> prefixes = new HashSet<>();
|
||||
|
||||
private final Set<String> queues = new HashSet<String>();
|
||||
|
||||
private final Set<String> exchanges = new HashSet<String>();
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory) {
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(connectionFactory);
|
||||
this(connectionFactory, new RabbitMessageChannelBinder(connectionFactory));
|
||||
}
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory, RabbitMessageChannelBinder binder) {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(1);
|
||||
@@ -60,75 +68,48 @@ public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBin
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindConsumer(name, moduleInputChannel, properties);
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
this.queues.add(prefix(properties) + name + (group == null ? ".default" : "." + group));
|
||||
this.exchanges.add(prefix(properties) + name);
|
||||
return super.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindProducer(name, moduleOutputChannel, properties);
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
this.queues.add(prefix(properties) + name + ".default");
|
||||
this.exchanges.add(prefix(properties) + name);
|
||||
return super.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, String group, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindPubSubConsumer(name, inputChannel, group, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel outputChannel, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindPubSubProducer(name, outputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindRequestor(name, requests, replies, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindReplier(name, requests, replies, properties);
|
||||
}
|
||||
|
||||
public void capturePrefix(Properties 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() {
|
||||
if (!queues.isEmpty()) {
|
||||
for (String queue : queues) {
|
||||
rabbitAdmin.deleteQueue(BINDER_PREFIX + queue);
|
||||
// delete any partitioned queues
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rabbitAdmin.deleteQueue(BINDER_PREFIX + queue + "-" + i);
|
||||
}
|
||||
for (String prefix : this.prefixes) {
|
||||
rabbitAdmin.deleteQueue(prefix + queue);
|
||||
// delete any partitioned queues
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rabbitAdmin.deleteQueue(prefix + queue + "-" + i);
|
||||
}
|
||||
rabbitAdmin.deleteExchange(prefix + queue);
|
||||
rabbitAdmin.deleteExchange(prefix + queue + ".requests");
|
||||
}
|
||||
rabbitAdmin.deleteExchange(BINDER_PREFIX + queue);
|
||||
for (String queue : this.queues) {
|
||||
this.rabbitAdmin.deleteQueue(queue);
|
||||
this.rabbitAdmin.deleteQueue(queue + ".dlq");
|
||||
// delete any partitioned queues
|
||||
for (int i = 0; i < 10; i++) {
|
||||
this.rabbitAdmin.deleteQueue(queue + "-" + i);
|
||||
this.rabbitAdmin.deleteQueue(queue + "-" + i + ".dlq");
|
||||
}
|
||||
}
|
||||
if (!topics.isEmpty()) {
|
||||
for (String exchange : topics) {
|
||||
rabbitAdmin.deleteExchange(BINDER_PREFIX + exchange);
|
||||
}
|
||||
for (String exchange : this.exchanges) {
|
||||
this.rabbitAdmin.deleteExchange(exchange);
|
||||
}
|
||||
for (String prefix : this.prefixes) {
|
||||
this.rabbitAdmin.deleteExchange(prefix + "DLX");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -65,8 +65,8 @@ public class RabbitBinderModuleTests {
|
||||
context = null;
|
||||
}
|
||||
RabbitAdmin admin = new RabbitAdmin(rabbitTestSupport.getResource());
|
||||
admin.deleteQueue("binder.input");
|
||||
admin.deleteQueue("binder.output");
|
||||
admin.deleteQueue("binder.input.default");
|
||||
admin.deleteQueue("binder.output.default");
|
||||
admin.deleteExchange("binder.input");
|
||||
admin.deleteExchange("binder.output");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -18,7 +18,9 @@ package org.springframework.cloud.stream.binder.redis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -31,6 +33,8 @@ import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
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;
|
||||
@@ -38,9 +42,7 @@ import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter;
|
||||
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
|
||||
import org.springframework.integration.redis.outbound.RedisPublishingMessageHandler;
|
||||
import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -56,6 +58,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by Redis.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
@@ -65,71 +68,30 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
|
||||
private static final String ERROR_HEADER = "errorKey";
|
||||
|
||||
private static final String CONSUMER_GROUPS_KEY_PREFIX = "groups.";
|
||||
|
||||
private static final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private final String[] headersToMap;
|
||||
|
||||
/**
|
||||
* Retry only.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PUBSUB_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.build();
|
||||
private final RedisOperations<String, String> redisOperations;
|
||||
|
||||
/**
|
||||
* Retry + concurrency.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_NAMED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Named + partitioning.
|
||||
* Retry + concurrency + partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
.addAll(SUPPORTED_NAMED_CONSUMER_PROPERTIES)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Retry + concurrency (request).
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_REPLYING_CONSUMER_PROPERTIES = new SetBuilder()
|
||||
// request
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.add(BinderPropertyKeys.PARTITION_INDEX)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* None.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PUBSUB_PRODUCER_PROPERTIES = PRODUCER_STANDARD_PROPERTIES;
|
||||
|
||||
/**
|
||||
* None.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_NAMED_PRODUCER_PROPERTIES = PRODUCER_STANDARD_PROPERTIES;
|
||||
|
||||
/**
|
||||
* Partitioning.
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
.addAll(PRODUCER_PARTITIONING_PROPERTIES)
|
||||
.addAll(PRODUCER_STANDARD_PROPERTIES)
|
||||
.add(BinderPropertyKeys.DIRECT_BINDING_ALLOWED)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Retry, concurrency (reply).
|
||||
*/
|
||||
private static final Set<Object> SUPPORTED_REQUESTING_PRODUCER_PROPERTIES = new SetBuilder()
|
||||
// reply
|
||||
.addAll(CONSUMER_RETRY_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.build();
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
@@ -143,11 +105,12 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
this(connectionFactory, new String[0]);
|
||||
}
|
||||
|
||||
public RedisMessageChannelBinder(RedisConnectionFactory connectionFactory,
|
||||
String... headersToMap) {
|
||||
public RedisMessageChannelBinder(RedisConnectionFactory connectionFactory, String... headersToMap) {
|
||||
Assert.notNull(connectionFactory, "connectionFactory must not be null");
|
||||
this.connectionFactory = connectionFactory;
|
||||
|
||||
StringRedisTemplate template = new StringRedisTemplate(connectionFactory);
|
||||
template.afterPropertiesSet();
|
||||
this.redisOperations = template;
|
||||
if (headersToMap != null && headersToMap.length > 0) {
|
||||
String[] combinedHeadersToMap =
|
||||
Arrays.copyOfRange(BinderHeaders.STANDARD_HEADERS, 0, BinderHeaders.STANDARD_HEADERS.length
|
||||
@@ -159,7 +122,6 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
else {
|
||||
this.headersToMap = BinderHeaders.STANDARD_HEADERS;
|
||||
}
|
||||
|
||||
this.errorAdapter = new RedisQueueOutboundChannelAdapter(
|
||||
parser.parseExpression("headers['" + ERROR_HEADER + "']"), connectionFactory);
|
||||
}
|
||||
@@ -173,23 +135,16 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(final String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_NAMED_CONSUMER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateConsumerProperties(name, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
}
|
||||
protected Binding<MessageChannel> doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
String queueName = "queue." + name;
|
||||
String queueName = groupedName(name, group);
|
||||
validateConsumerProperties(queueName, properties, SUPPORTED_CONSUMER_PROPERTIES);
|
||||
int partitionIndex = accessor.getPartitionIndex();
|
||||
if (partitionIndex >= 0) {
|
||||
queueName += "-" + partitionIndex;
|
||||
}
|
||||
MessageProducerSupport adapter = createInboundAdapter(accessor, queueName);
|
||||
doRegisterConsumer(name, name + (partitionIndex >= 0 ? "-" + partitionIndex : ""), moduleInputChannel, adapter,
|
||||
accessor);
|
||||
bindExistingProducerDirectlyIfPossible(name, moduleInputChannel);
|
||||
return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, accessor);
|
||||
}
|
||||
|
||||
private MessageProducerSupport createInboundAdapter(RedisPropertiesAccessor accessor, String queueName) {
|
||||
@@ -209,37 +164,25 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(final String name, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("declaring pubsub for inbound: " + name);
|
||||
}
|
||||
validateConsumerProperties(name, properties, SUPPORTED_PUBSUB_CONSUMER_PROPERTIES);
|
||||
RedisInboundChannelAdapter adapter = new RedisInboundChannelAdapter(this.connectionFactory);
|
||||
adapter.setBeanFactory(this.getBeanFactory());
|
||||
adapter.setSerializer(null);
|
||||
adapter.setTopics(applyPubSub(name));
|
||||
doRegisterConsumer(name, name, moduleInputChannel, adapter, new RedisPropertiesAccessor(properties));
|
||||
}
|
||||
|
||||
private void doRegisterConsumer(String bindingName, String channelName, MessageChannel moduleInputChannel,
|
||||
private Binding<MessageChannel> doRegisterConsumer(String bindingName, String group, String channelName, MessageChannel moduleInputChannel,
|
||||
MessageProducerSupport adapter, RedisPropertiesAccessor properties) {
|
||||
DirectChannel bridgeToModuleChannel = new DirectChannel();
|
||||
bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
|
||||
bridgeToModuleChannel.setBeanName(channelName + ".bridge");
|
||||
MessageChannel bridgeInputChannel = addRetryIfNeeded(channelName, bridgeToModuleChannel, properties);
|
||||
adapter.setOutputChannel(bridgeInputChannel);
|
||||
adapter.setBeanName("inbound." + bindingName);
|
||||
adapter.setBeanName("inbound." + channelName);
|
||||
adapter.afterPropertiesSet();
|
||||
Binding consumerBinding = Binding.forConsumer(bindingName, adapter, moduleInputChannel, properties);
|
||||
Binding<MessageChannel> consumerBinding = Binding.forConsumer(channelName, group, adapter, moduleInputChannel, properties);
|
||||
addBinding(consumerBinding);
|
||||
ReceivingHandler convertingBridge = new ReceivingHandler();
|
||||
convertingBridge.setOutputChannel(moduleInputChannel);
|
||||
convertingBridge.setBeanName(channelName + ".bridge.handler");
|
||||
convertingBridge.afterPropertiesSet();
|
||||
bridgeToModuleChannel.subscribe(convertingBridge);
|
||||
this.redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + bindingName).incrementScore(group, 1);
|
||||
consumerBinding.start();
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,101 +243,49 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_NAMED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
else {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
}
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
if (!bindNewProducerDirectlyIfPossible(name, (SubscribableChannel) moduleOutputChannel, accessor)) {
|
||||
String partitionKeyExtractorClass = accessor.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = accessor.getPartitionKeyExpression();
|
||||
RedisQueueOutboundChannelAdapter queue;
|
||||
String queueName = "queue." + name;
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
queue = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
|
||||
}
|
||||
else {
|
||||
queue = new RedisQueueOutboundChannelAdapter(
|
||||
parser.parseExpression(buildPartitionRoutingExpression(queueName)), this.connectionFactory);
|
||||
}
|
||||
queue.setIntegrationEvaluationContext(this.evaluationContext);
|
||||
queue.setBeanFactory(this.getBeanFactory());
|
||||
queue.afterPropertiesSet();
|
||||
doRegisterProducer(name, moduleOutputChannel, queue, accessor);
|
||||
protected void afterUnbind(Binding<MessageChannel> binding) {
|
||||
if (Binding.Type.consumer.equals(binding.getType())) {
|
||||
String key = CONSUMER_GROUPS_KEY_PREFIX + binding.getName();
|
||||
this.redisOperations.boundZSetOps(key).incrementScore(binding.getGroup(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PUBSUB_PRODUCER_PROPERTIES);
|
||||
RedisPublishingMessageHandler topic = new RedisPublishingMessageHandler(connectionFactory);
|
||||
topic.setBeanFactory(this.getBeanFactory());
|
||||
topic.setTopic(applyPubSub(name));
|
||||
topic.afterPropertiesSet();
|
||||
doRegisterProducer(name, moduleOutputChannel, topic, new RedisPropertiesAccessor(properties));
|
||||
}
|
||||
|
||||
private void doRegisterProducer(final String name, MessageChannel moduleOutputChannel, MessageHandler delegate,
|
||||
RedisPropertiesAccessor properties) {
|
||||
this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
|
||||
}
|
||||
|
||||
private void doRegisterProducer(final String name, MessageChannel moduleOutputChannel, MessageHandler delegate,
|
||||
String replyTo, RedisPropertiesAccessor properties) {
|
||||
public Binding<MessageChannel> bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
MessageHandler handler = new SendingHandler(delegate, replyTo, properties);
|
||||
validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES);
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
return doRegisterProducer(name, moduleOutputChannel, accessor);
|
||||
}
|
||||
|
||||
private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, RedisPropertiesAccessor accessor) {
|
||||
String partitionKeyExtractorClass = accessor.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = accessor.getPartitionKeyExpression();
|
||||
RedisQueueOutboundChannelAdapter queue;
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
queue = new RedisQueueOutboundChannelAdapter(name, this.connectionFactory);
|
||||
}
|
||||
else {
|
||||
queue = new RedisQueueOutboundChannelAdapter(
|
||||
parser.parseExpression(buildPartitionRoutingExpression(name)), this.connectionFactory);
|
||||
}
|
||||
queue.setIntegrationEvaluationContext(this.evaluationContext);
|
||||
queue.setBeanFactory(this.getBeanFactory());
|
||||
queue.afterPropertiesSet();
|
||||
return queue;
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel, RedisPropertiesAccessor 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();
|
||||
Binding producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer, properties);
|
||||
Binding<MessageChannel> producerBinding = Binding.forProducer(name, moduleOutputChannel, consumer, properties);
|
||||
addBinding(producerBinding);
|
||||
producerBinding.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("binding requestor: " + name);
|
||||
}
|
||||
Assert.isInstanceOf(SubscribableChannel.class, requests);
|
||||
validateProducerProperties(name, properties, SUPPORTED_REQUESTING_PRODUCER_PROPERTIES);
|
||||
RedisQueueOutboundChannelAdapter queue = new RedisQueueOutboundChannelAdapter("queue." + applyRequests(name),
|
||||
this.connectionFactory);
|
||||
queue.setBeanFactory(this.getBeanFactory());
|
||||
queue.afterPropertiesSet();
|
||||
String replyQueueName = name + ".replies." + this.getIdGenerator().generateId();
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
this.doRegisterProducer(name, requests, queue, replyQueueName, accessor);
|
||||
MessageProducerSupport adapter = createInboundAdapter(accessor, replyQueueName);
|
||||
this.doRegisterConsumer(name, name, replies, adapter, accessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("binding replier: " + name);
|
||||
}
|
||||
validateConsumerProperties(name, properties, SUPPORTED_REPLYING_CONSUMER_PROPERTIES);
|
||||
RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties);
|
||||
MessageProducerSupport adapter = createInboundAdapter(accessor, "queue." + applyRequests(name));
|
||||
this.doRegisterConsumer(name, name, requests, adapter, accessor);
|
||||
|
||||
RedisQueueOutboundChannelAdapter replyQueue = new RedisQueueOutboundChannelAdapter(
|
||||
RedisMessageChannelBinder.parser.parseExpression("headers['" + BinderHeaders.REPLY_TO + "']"),
|
||||
this.connectionFactory);
|
||||
replyQueue.setBeanFactory(this.getBeanFactory());
|
||||
replyQueue.setIntegrationEvaluationContext(this.evaluationContext);
|
||||
replyQueue.afterPropertiesSet();
|
||||
this.doRegisterProducer(name, replies, replyQueue, accessor);
|
||||
return producerBinding;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -404,37 +295,48 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
|
||||
private class SendingHandler extends AbstractMessageHandler {
|
||||
|
||||
private final MessageHandler delegate;
|
||||
|
||||
private final String replyTo;
|
||||
private final String bindingName;
|
||||
|
||||
private final PartitioningMetadata partitioningMetadata;
|
||||
|
||||
private final RedisPropertiesAccessor accessor;
|
||||
|
||||
private SendingHandler(MessageHandler delegate, String replyTo, RedisPropertiesAccessor properties) {
|
||||
this.delegate = delegate;
|
||||
this.replyTo = replyTo;
|
||||
private final Map<String, RedisQueueOutboundChannelAdapter> adapters = new HashMap<>();
|
||||
|
||||
private SendingHandler(String bindingName, RedisPropertiesAccessor properties) {
|
||||
this.bindingName = bindingName;
|
||||
this.accessor = properties;
|
||||
this.partitioningMetadata = new PartitioningMetadata(properties, properties.getNextModuleCount());
|
||||
this.setBeanFactory(RedisMessageChannelBinder.this.getBeanFactory());
|
||||
refreshChannelAdapters();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
MessageValues transformed = serializePayloadIfNecessary(message);
|
||||
|
||||
if (replyTo != null) {
|
||||
transformed.put(BinderHeaders.REPLY_TO, this.replyTo);
|
||||
}
|
||||
if (this.partitioningMetadata.isPartitionedModule()) {
|
||||
|
||||
transformed.put(PARTITION_HEADER, determinePartition(message, this.partitioningMetadata));
|
||||
}
|
||||
|
||||
byte[] messageToSend = embeddedHeadersMessageConverter.embedHeaders(transformed,
|
||||
RedisMessageChannelBinder.this.headersToMap);
|
||||
delegate.handleMessage(MessageBuilder.withPayload(messageToSend).copyHeaders(transformed).build());
|
||||
|
||||
refreshChannelAdapters();
|
||||
for (RedisQueueOutboundChannelAdapter adapter : adapters.values()) {
|
||||
adapter.handleMessage((MessageBuilder.withPayload(messageToSend).copyHeaders(transformed).build()));
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshChannelAdapters() {
|
||||
Set<String> groups = redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + bindingName).rangeByScore(1, Double.MAX_VALUE);
|
||||
for (String group : groups) {
|
||||
if (!adapters.containsKey(group)) {
|
||||
String channel = String.format("%s.%s", this.bindingName, group);
|
||||
adapters.put(group, createProducerEndpoint(channel, accessor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReceivingHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.redis;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -24,17 +23,15 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
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.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -52,6 +49,7 @@ import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
|
||||
import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
@@ -60,6 +58,7 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@@ -86,34 +85,20 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
@Ignore("https://github.com/spring-cloud/spring-cloud-stream/issues/247")
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
|
||||
//TimeUnit.SECONDS.sleep(2);
|
||||
|
||||
super.testSendAndReceivePubSub();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
createTemplate().boundListOps("queue.direct.0").trim(1, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
binder.bindConsumer("props.0", new DirectChannel(), properties);
|
||||
Binding<MessageChannel> binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
assertEquals(binding, bindings.get(0));
|
||||
AbstractEndpoint endpoint = binding.getEndpoint();
|
||||
assertThat(endpoint, instanceOf(RedisQueueMessageDrivenEndpoint.class));
|
||||
assertSame(DirectChannel.class, TestUtils.getPropertyValue(endpoint, "outputChannel").getClass());
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbind(binding);
|
||||
assertEquals(0, bindings.size());
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
@@ -123,48 +108,34 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
properties.put("maxAttempts", "23");
|
||||
properties.put("partitionIndex", 0);
|
||||
|
||||
binder.bindConsumer("props.0", new DirectChannel(), properties);
|
||||
binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
assertEquals(1, bindings.size());
|
||||
endpoint = bindings.get(0).getEndpoint();
|
||||
assertEquals(binding, bindings.get(0));
|
||||
endpoint = binding.getEndpoint();
|
||||
verifyConsumer(endpoint);
|
||||
|
||||
try {
|
||||
binder.bindPubSubConsumer("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support consumer properties: "),
|
||||
containsString("partitionIndex"),
|
||||
containsString("concurrency"),
|
||||
containsString(" for dummy.")));
|
||||
}
|
||||
try {
|
||||
binder.bindConsumer("queue:dummy", null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertEquals(getClassUnderTestName() + " does not support consumer property: partitionIndex for queue:dummy.",
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbind(binding);
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(2, bindings.size());
|
||||
assertEquals(producerBinding, bindings.get(1));
|
||||
AbstractEndpoint endpoint = producerBinding.getEndpoint();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, RedisQueueOutboundChannelAdapter> adapters = TestUtils.getPropertyValue(endpoint, "handler.adapters", Map.class);
|
||||
RedisQueueOutboundChannelAdapter adapter = adapters.get("test");
|
||||
assertEquals(
|
||||
"queue.props.0",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.queueNameExpression", Expression.class).getExpressionString());
|
||||
binder.unbindProducers("props.0");
|
||||
assertEquals(0, bindings.size());
|
||||
"props.0.test",
|
||||
TestUtils.getPropertyValue(adapter, "queueNameExpression", Expression.class).getExpressionString());
|
||||
binder.unbind(producerBinding);
|
||||
assertEquals(1, bindings.size());
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
@@ -173,138 +144,16 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
|
||||
binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
assertEquals(1, bindings.size());
|
||||
endpoint = bindings.get(0).getEndpoint();
|
||||
assertEquals(
|
||||
"'queue.props.0-' + headers['partition']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.queueNameExpression", Expression.class).getExpressionString());
|
||||
|
||||
try {
|
||||
binder.bindPubSubProducer("dummy", null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), containsString("for dummy."));
|
||||
}
|
||||
try {
|
||||
binder.bindProducer("queue:dummy", new DirectChannel(), properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), containsString("for queue:dummy."));
|
||||
}
|
||||
|
||||
binder.unbindProducers("props.0");
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestReplyRequestorProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
properties.put("backOffMaxInterval", "20000");
|
||||
properties.put("backOffMultiplier", "5.0");
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("maxAttempts", "23");
|
||||
|
||||
binder.bindRequestor("props.0", new DirectChannel(), new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
assertEquals(2, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint(); // producer
|
||||
endpoint = bindings.get(1).getEndpoint();
|
||||
adapter = (RedisQueueOutboundChannelAdapter) TestUtils.getPropertyValue(endpoint, "handler.adapters", Map.class).get("test");
|
||||
assertEquals(
|
||||
"queue.props.0.requests",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.queueNameExpression", Expression.class).getExpressionString());
|
||||
"'props.0.test-' + headers['partition']",
|
||||
TestUtils.getPropertyValue(adapter, "queueNameExpression", Expression.class).getExpressionString());
|
||||
|
||||
endpoint = bindings.get(1).getEndpoint(); // consumer
|
||||
verifyConsumer(endpoint);
|
||||
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put("partitionIndex", "0");
|
||||
try {
|
||||
binder.bindRequestor("dummy", new DirectChannel(), new DirectChannel(), properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support producer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), allOf(containsString("partitionIndex"), containsString("for dummy.")));
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbindProducers("props.0");
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestReplyReplierProperties() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
properties.put("backOffMaxInterval", "20000");
|
||||
properties.put("backOffMultiplier", "5.0");
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("maxAttempts", "23");
|
||||
|
||||
binder.bindReplier("props.0", new DirectChannel(), new DirectChannel(), properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
|
||||
assertEquals(2, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(1).getEndpoint(); // producer
|
||||
assertEquals(
|
||||
"headers['replyTo']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.queueNameExpression", Expression.class).getExpressionString());
|
||||
|
||||
endpoint = bindings.get(0).getEndpoint(); // consumer
|
||||
verifyConsumer(endpoint);
|
||||
|
||||
properties.put("partitionKeyExpression", "'foo'");
|
||||
properties.put("partitionKeyExtractorClass", "foo");
|
||||
properties.put("partitionSelectorExpression", "0");
|
||||
properties.put("partitionSelectorClass", "foo");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1");
|
||||
properties.put("partitionIndex", "0");
|
||||
try {
|
||||
binder.bindReplier("dummy", new DirectChannel(), new DirectChannel(), properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), allOf(
|
||||
containsString(getClassUnderTestName() + " does not support consumer properties: "),
|
||||
containsString("partitionSelectorExpression"),
|
||||
containsString("partitionKeyExtractorClass"),
|
||||
containsString("partitionKeyExpression"),
|
||||
containsString("partitionSelectorClass")));
|
||||
assertThat(e.getMessage(), allOf(containsString("partitionIndex"), containsString("for dummy.")));
|
||||
}
|
||||
|
||||
binder.unbindConsumers("props.0");
|
||||
binder.unbindProducers("props.0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
assertEquals(0, bindings.size());
|
||||
}
|
||||
|
||||
@@ -335,12 +184,13 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
props.put("maxAttempts", 2);
|
||||
props.put("backOffInitialInterval", 100);
|
||||
props.put("backOffMultiplier", "1.0");
|
||||
binder.bindConsumer("retry.0", new DirectChannel(), props); // no subscriber
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), props); // no subscriber
|
||||
channel.send(new GenericMessage<String>("foo"));
|
||||
RedisTemplate<String, Object> template = createTemplate();
|
||||
Object rightPop = template.boundListOps("ERRORS:retry.0").rightPop(5, TimeUnit.SECONDS);
|
||||
Object rightPop = template.boundListOps("ERRORS:retry.0.test").rightPop(5, TimeUnit.SECONDS);
|
||||
assertNotNull(rightPop);
|
||||
assertThat(new String((byte[]) rightPop), containsString("foo"));
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -352,10 +202,6 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
assertTrue(headers.contains("bar"));
|
||||
}
|
||||
|
||||
@Override @Ignore("https://github.com/spring-cloud/spring-cloud-stream/issues/247")
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
}
|
||||
|
||||
private RedisTemplate<String, Object> createTemplate() {
|
||||
if (this.redisTemplate != null) {
|
||||
return this.redisTemplate;
|
||||
@@ -370,13 +216,15 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected String getEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.queueNameExpression", Expression.class).getExpressionString();
|
||||
Map<String, RedisQueueOutboundChannelAdapter> adapters = TestUtils.getPropertyValue(endpoint, "handler.adapters", Map.class);
|
||||
return TestUtils.getPropertyValue(adapters.values().iterator().next(), "queueNameExpression", Expression.class).getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.topicExpression", Expression.class).getExpressionString();
|
||||
protected String getExpectedRoutingBaseDestination(String name, String group) {
|
||||
return name + "." + group;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -391,7 +239,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Override
|
||||
public Object receive(boolean expectNull) throws Exception {
|
||||
byte[] bytes = (byte[]) template.boundListOps("queue." + queue).rightPop(50, TimeUnit.MILLISECONDS);
|
||||
byte[] bytes = (byte[]) template.boundListOps(queue).rightPop(50, TimeUnit.MILLISECONDS);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,18 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.redis;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
|
||||
|
||||
/**
|
||||
* Test support class for {@link RedisMessageChannelBinder}.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -17,19 +17,15 @@
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
@@ -45,6 +41,7 @@ import org.springframework.messaging.MessageHeaders;
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractBinderTests {
|
||||
|
||||
@@ -55,20 +52,20 @@ public abstract class AbstractBinderTests {
|
||||
@Test
|
||||
public void testClean() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
binder.bindProducer("foo.0", new DirectChannel(), null);
|
||||
binder.bindConsumer("foo.0", new DirectChannel(), null);
|
||||
binder.bindProducer("foo.1", new DirectChannel(), null);
|
||||
binder.bindConsumer("foo.1", new DirectChannel(), null);
|
||||
binder.bindProducer("foo.2", new DirectChannel(), null);
|
||||
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);
|
||||
Collection<?> bindings = getBindings(binder);
|
||||
assertEquals(5, bindings.size());
|
||||
binder.unbindProducers("foo.0");
|
||||
binder.unbind(foo0ProducerBinding);
|
||||
assertEquals(4, bindings.size());
|
||||
binder.unbindConsumers("foo.0");
|
||||
binder.unbindProducers("foo.1");
|
||||
binder.unbind(foo0ConsumerBinding);
|
||||
binder.unbind(foo1ProducerBinding);
|
||||
assertEquals(2, bindings.size());
|
||||
binder.unbindConsumers("foo.1");
|
||||
binder.unbindProducers("foo.2");
|
||||
binder.unbind(foo1ConsumerBinding);
|
||||
binder.unbind(foo2ProducerBinding);
|
||||
assertTrue(bindings.isEmpty());
|
||||
}
|
||||
|
||||
@@ -77,8 +74,8 @@ public abstract class AbstractBinderTests {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("foo.0", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
@@ -89,8 +86,8 @@ public abstract class AbstractBinderTests {
|
||||
assertEquals("foo", inbound.getPayload());
|
||||
assertNull(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
binder.unbindProducers("foo.0");
|
||||
binder.unbindConsumers("foo.0");
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,8 +95,8 @@ public abstract class AbstractBinderTests {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("bar.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("bar.0", moduleInputChannel, null);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, null);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, null);
|
||||
binderBindUnbindLatency();
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("foo").build();
|
||||
@@ -109,164 +106,13 @@ public abstract class AbstractBinderTests {
|
||||
assertEquals("foo", inbound.getPayload());
|
||||
assertNull(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertNull(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
binder.unbindProducers("bar.0");
|
||||
binder.unbindConsumers("bar.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, "tgroup1", null);
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, "tgroup2", null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
while (!success) {
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = moduleInputChannel.receive(5000);
|
||||
assertNotNull(inbound);
|
||||
assertEquals("foo", inbound.getPayload());
|
||||
assertNull(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
Message<?> tapped1 = module2InputChannel.receive(5000);
|
||||
Message<?> tapped2 = module3InputChannel.receive(5000);
|
||||
if (tapped1 == null || tapped2 == null) {
|
||||
// listener may not have started
|
||||
assertFalse("Failed to receive tap after retry", retried);
|
||||
retried = true;
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
assertEquals("foo", tapped1.getPayload());
|
||||
assertNull(tapped1.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", tapped1.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
assertEquals("foo", tapped2.getPayload());
|
||||
assertNull(tapped2.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", tapped2.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindPubSubConsumers(barTapName, "tgroup2");
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
moduleOutputChannel.send(message2);
|
||||
|
||||
// other tap still receives messages
|
||||
Message<?> tapped = module2InputChannel.receive(5000);
|
||||
assertNotNull(tapped);
|
||||
|
||||
// Removed tap does not
|
||||
assertNull(module3InputChannel.receive(1000));
|
||||
|
||||
// when other tap stream is deleted
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindPubSubConsumers(fooTapName, "tgroup1");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
// Create the tap first
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, "tgroup1", null);
|
||||
|
||||
// Then create the stream
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, "tgroup2", null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
while (!success) {
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = moduleInputChannel.receive(5000);
|
||||
assertNotNull(inbound);
|
||||
assertEquals("foo", inbound.getPayload());
|
||||
assertNull(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
Message<?> tapped1 = module2InputChannel.receive(5000);
|
||||
Message<?> tapped2 = module3InputChannel.receive(5000);
|
||||
if (tapped1 == null || tapped2 == null) {
|
||||
// listener may not have started
|
||||
assertFalse("Failed to receive tap after retry", retried);
|
||||
retried = true;
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
assertEquals("foo", tapped1.getPayload());
|
||||
assertNull(tapped1.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", tapped1.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
assertEquals("foo", tapped2.getPayload());
|
||||
assertNull(tapped2.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE));
|
||||
assertEquals("foo/bar", tapped2.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindPubSubConsumers(barTapName, "tgroup2");
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
moduleOutputChannel.send(message2);
|
||||
|
||||
// other tap still receives messages
|
||||
Message<?> tapped = module2InputChannel.receive(5000);
|
||||
assertNotNull(tapped);
|
||||
|
||||
// Removed tap does not
|
||||
assertNull(module3InputChannel.receive(1000));
|
||||
|
||||
// when other tap stream is deleted
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindPubSubConsumers(fooTapName, "tgroup1");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadDynamic() throws Exception {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, "'foo'");
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
try {
|
||||
binder.bindDynamicProducer("queue:foo", properties);
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (BinderException mbe) {
|
||||
Assert.assertEquals("Failed to bind dynamic channel 'queue:foo' with properties " +
|
||||
"{partitionKeyExpression='foo'}",
|
||||
mbe.getMessage());
|
||||
if (binder instanceof AbstractTestBinder) {
|
||||
binder = ((AbstractTestBinder) binder).getCoreBinder();
|
||||
}
|
||||
assertFalse(((MessageChannelBinderSupport) binder).getApplicationContext().containsBean("queue:foo"));
|
||||
}
|
||||
binder.unbind(producerBinding);
|
||||
binder.unbind(consumerBinding);
|
||||
}
|
||||
|
||||
protected Collection<?> getBindings(Binder<MessageChannel> testBinder) {
|
||||
if (testBinder instanceof AbstractTestBinder) {
|
||||
return getBindingsFromBinder(((AbstractTestBinder) testBinder).getCoreBinder());
|
||||
return getBindingsFromBinder(((AbstractTestBinder<?>) testBinder).getCoreBinder());
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -22,19 +22,17 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class that adds test support for {@link Binder}.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport> implements Binder<MessageChannel> {
|
||||
|
||||
protected Set<String> queues = new HashSet<String>();
|
||||
|
||||
protected Set<String> topics = new HashSet<String>();
|
||||
|
||||
private C binder;
|
||||
|
||||
public void setBinder(C binder) {
|
||||
@@ -48,45 +46,15 @@ public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
binder.bindConsumer(name, moduleInputChannel, properties);
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) {
|
||||
queues.add(name);
|
||||
return binder.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, String group, Properties properties) {
|
||||
binder.bindPubSubConsumer(name, inputChannel, group, properties);
|
||||
addTopic(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
binder.bindProducer(name, moduleOutputChannel, properties);
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
queues.add(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel outputChannel, Properties properties) {
|
||||
binder.bindPubSubProducer(name, outputChannel, properties);
|
||||
addTopic(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
binder.bindRequestor(name, requests, replies, properties);
|
||||
queues.add(name + ".requests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
binder.bindReplier(name, requests, replies, properties);
|
||||
queues.add(name + ".requests");
|
||||
}
|
||||
|
||||
private void addTopic(String topicName) {
|
||||
topics.add("topic." + topicName);
|
||||
return binder.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
public C getCoreBinder() {
|
||||
@@ -96,40 +64,8 @@ public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport>
|
||||
public abstract void cleanup();
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
binder.unbindConsumers(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
binder.unbindPubSubConsumers(name, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
binder.unbindProducers(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, MessageChannel channel) {
|
||||
binder.unbindConsumer(name, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, MessageChannel channel) {
|
||||
binder.unbindProducer(name, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel bindDynamicProducer(String name, Properties properties) {
|
||||
this.queues.add(name);
|
||||
return this.binder.bindDynamicProducer(name, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
this.topics.add(name);
|
||||
return this.binder.bindDynamicPubSubProducer(name, properties);
|
||||
public void unbind(Binding<MessageChannel> binding) {
|
||||
binder.unbind(binding);
|
||||
}
|
||||
|
||||
public C getBinder() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,91 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for binders that use an external broker.
|
||||
*
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public abstract class BrokerBinderTests extends
|
||||
AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testDirectBinding() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(BinderPropertyKeys.DIRECT_BINDING_ALLOWED, "true");
|
||||
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("direct.input");
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
moduleOutputChannel.setBeanName("direct.output");
|
||||
binder.bindConsumer("direct.0", moduleInputChannel, null);
|
||||
binder.bindProducer("direct.0", moduleOutputChannel, properties);
|
||||
|
||||
final AtomicReference<Thread> caller = new AtomicReference<Thread>();
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
caller.set(Thread.currentThread());
|
||||
count.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
moduleOutputChannel.send(new GenericMessage<String>("foo"));
|
||||
moduleOutputChannel.send(new GenericMessage<String>("foo"));
|
||||
|
||||
assertNotNull(caller.get());
|
||||
assertSame(Thread.currentThread(), caller.get());
|
||||
assertEquals(2, count.get());
|
||||
assertNull(spyOn("direct.0").receive(true));
|
||||
|
||||
// Remove direct binding and bind the producer
|
||||
binder.unbindConsumers("direct.0");
|
||||
binderBindUnbindLatency();
|
||||
|
||||
Spy spy = spyOn("direct.0");
|
||||
count.set(0);
|
||||
moduleOutputChannel.send(new GenericMessage<String>("bar"));
|
||||
moduleOutputChannel.send(new GenericMessage<String>("baz"));
|
||||
Object bar = spy.receive(false);
|
||||
assertEquals("bar", bar);
|
||||
Object baz = spy.receive(false);
|
||||
assertEquals("baz", baz);
|
||||
assertEquals(0, count.get());
|
||||
|
||||
// Unbind producer from binder and bind directly again
|
||||
caller.set(null);
|
||||
binder.bindConsumer("direct.0", moduleInputChannel, null);
|
||||
moduleOutputChannel.send(new GenericMessage<String>("foo"));
|
||||
moduleOutputChannel.send(new GenericMessage<String>("foo"));
|
||||
assertNotNull(caller.get());
|
||||
assertSame(Thread.currentThread(), caller.get());
|
||||
assertEquals(2, count.get());
|
||||
assertNull(spy.receive(true));
|
||||
|
||||
binder.unbindProducers("direct.0");
|
||||
binder.unbindConsumers("direct.0");
|
||||
}
|
||||
public abstract class BrokerBinderTests extends AbstractBinderTests {
|
||||
|
||||
/**
|
||||
* Create a new spy on the given 'queue'. This allows de-correlating the creation of
|
||||
@@ -109,6 +30,4 @@ public abstract class BrokerBinderTests extends
|
||||
*/
|
||||
public abstract Spy spyOn(final String name);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -43,11 +43,11 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for binders that support partitioning.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@@ -67,58 +67,59 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
+ " does not support producer "),
|
||||
containsString("foo"),
|
||||
containsString("baz"),
|
||||
containsString(" for badprops.0.")));
|
||||
containsString(" for badprops.0")));
|
||||
}
|
||||
|
||||
properties.remove("baz");
|
||||
try {
|
||||
binder.bindConsumer("badprops.0", output, properties);
|
||||
binder.bindConsumer("badprops.0", "test", output, properties);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage(), equalTo(getClassUnderTestName()
|
||||
+ " does not support consumer property: foo for badprops.0."));
|
||||
+ " does not support consumer property: foo for badprops.0.test."));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "payload");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put("concurrency", "2");
|
||||
consumerProperties.put("partitionIndex", "0");
|
||||
consumerProperties.put("count","3");
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "1");
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "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");
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
binder.bindProducer("part.0", output, properties);
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("part.0", output, producerProperties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(4, bindings.size());
|
||||
try {
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
assertThat(getEndpointRouting(endpoint), containsString("part.0-' + headers['partition']"));
|
||||
AbstractEndpoint endpoint = bindings.get(3).getEndpoint();
|
||||
assertThat(getEndpointRouting(endpoint), containsString(
|
||||
getExpectedRoutingBaseDestination("part.0", "test") + "-' + headers['partition']"));
|
||||
}
|
||||
catch (UnsupportedOperationException ignored) {
|
||||
|
||||
}
|
||||
|
||||
properties.clear();
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("partitionIndex", "0");
|
||||
properties.put("count","3");
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
binder.bindConsumer("part.0", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
binder.bindConsumer("part.0", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2S");
|
||||
binder.bindConsumer("part.0", input2, properties);
|
||||
|
||||
Message<Integer> message2 = MessageBuilder.withPayload(2)
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 42)
|
||||
@@ -148,17 +149,13 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
if (usesExplicitRouting()) {
|
||||
assertEquals(0, receive0.getPayload());
|
||||
assertEquals(1, receive1.getPayload());
|
||||
assertEquals(2, receive2.getPayload());
|
||||
|
||||
assertThat(receive2, fooMatcher);
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
assertThat(Arrays.asList(
|
||||
(Integer) receive0.getPayload(),
|
||||
(Integer) receive1.getPayload(),
|
||||
@@ -176,46 +173,48 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
containsOur3Messages);
|
||||
|
||||
}
|
||||
|
||||
binder.unbindConsumers("part.0");
|
||||
binder.unbindProducers("part.0");
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(input2Binding);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3");
|
||||
properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2");
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
binder.bindProducer("partJ.0", output, properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(1, bindings.size());
|
||||
if (usesExplicitRouting()) {
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint();
|
||||
assertThat(getEndpointRouting(endpoint), containsString("partJ.0-' + headers['partition']"));
|
||||
}
|
||||
|
||||
properties.clear();
|
||||
properties.put("concurrency", "2");
|
||||
properties.put("count","3");
|
||||
properties.put("partitionIndex", "0");
|
||||
Properties consumerProperties = new Properties();
|
||||
consumerProperties.put("concurrency", "2");
|
||||
consumerProperties.put("count","3");
|
||||
consumerProperties.put("partitionIndex", "0");
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0J");
|
||||
binder.bindConsumer("partJ.0", input0, properties);
|
||||
properties.put("partitionIndex", "1");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "1");
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1J");
|
||||
binder.bindConsumer("partJ.0", input1, properties);
|
||||
properties.put("partitionIndex", "2");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties);
|
||||
consumerProperties.put("partitionIndex", "2");
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2J");
|
||||
binder.bindConsumer("partJ.0", input2, properties);
|
||||
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");
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partJ.0", output, producerProperties);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding<MessageChannel>> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
assertEquals(4, bindings.size());
|
||||
if (usesExplicitRouting()) {
|
||||
AbstractEndpoint endpoint = bindings.get(3).getEndpoint();
|
||||
assertThat(getEndpointRouting(endpoint), containsString(
|
||||
getExpectedRoutingBaseDestination("partJ.0", "test") + "-' + headers['partition']"));
|
||||
}
|
||||
|
||||
output.send(new GenericMessage<Integer>(2));
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
@@ -242,8 +241,10 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
containsInAnyOrder(0, 1, 2));
|
||||
}
|
||||
|
||||
binder.unbindConsumers("partJ.0");
|
||||
binder.unbindProducers("partJ.0");
|
||||
binder.unbind(input0Binding);
|
||||
binder.unbind(input1Binding);
|
||||
binder.unbind(input2Binding);
|
||||
binder.unbind(outputBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,6 +262,14 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* For implementations that rely on explicit routing, return the expected base destination
|
||||
* (the part that precedes '-partition' within the expression).
|
||||
*/
|
||||
protected String getExpectedRoutingBaseDestination(String name, String group) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* For implementations that rely on explicit routing, return the routing expression.
|
||||
*/
|
||||
|
||||
@@ -269,31 +269,13 @@ public class MessageChannelBinderSupportTests {
|
||||
public class TestMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel channel, Properties properties) {
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel channel, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, MessageChannel channel, Properties properties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel channel, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -76,8 +76,8 @@ public class TwoKafkaBindersApplicationTest {
|
||||
binderFactory.getBinder("kafka1").bindProducer("dataIn", dataProducer, null);
|
||||
|
||||
QueueChannel dataConsumer = new QueueChannel();
|
||||
binderFactory.getBinder("kafka2").bindPubSubConsumer("dataOut", dataConsumer,
|
||||
UUID.randomUUID().toString(), null);
|
||||
binderFactory.getBinder("kafka2").bindConsumer("dataOut", UUID.randomUUID().toString(),
|
||||
dataConsumer, null);
|
||||
|
||||
String testPayload = "testFoo" + UUID.randomUUID().toString();
|
||||
dataProducer.send(MessageBuilder.withPayload(testPayload).build());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -60,10 +60,13 @@ public class RabbitAndRedisBinderApplicationTests {
|
||||
@Autowired
|
||||
private BinderFactory<MessageChannel> binderFactory;
|
||||
|
||||
private final String randomGroup = UUID.randomUUID().toString();
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
RabbitAdmin admin = new RabbitAdmin(rabbitTestSupport.getResource());
|
||||
admin.deleteQueue("binder.dataOut");
|
||||
admin.deleteQueue("binder.dataOut.default");
|
||||
admin.deleteQueue("binder.dataOut." + this.randomGroup);
|
||||
admin.deleteExchange("binder.dataOut");
|
||||
}
|
||||
|
||||
@@ -77,10 +80,10 @@ public class RabbitAndRedisBinderApplicationTests {
|
||||
binderFactory.getBinder("redis").bindProducer("dataIn", dataProducer, null);
|
||||
|
||||
QueueChannel dataConsumer = new QueueChannel();
|
||||
binderFactory.getBinder("rabbit").bindPubSubConsumer("dataOut", dataConsumer,
|
||||
UUID.randomUUID().toString(), null);
|
||||
binderFactory.getBinder("rabbit").bindConsumer("dataOut", this.randomGroup,
|
||||
dataConsumer, null);
|
||||
|
||||
String testPayload = "testFoo" + UUID.randomUUID().toString();
|
||||
String testPayload = "testFoo" + this.randomGroup;
|
||||
dataProducer.send(MessageBuilder.withPayload(testPayload).build());
|
||||
|
||||
Message<?> receive = dataConsumer.receive(2000);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -23,6 +23,7 @@ import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.test.matcher.MessageQueueMatcher;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
* @see MessageQueueMatcher
|
||||
*/
|
||||
public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
@@ -47,78 +49,29 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel inboundBindTarget, Properties properties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inboundBindTarget, String group, Properties properties) {
|
||||
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single subscriber to the channel, that enqueues messages for later retrieval and assertion in tests.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) {
|
||||
final BlockingQueue queue = messageCollector.register(outboundBindTarget);
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) {
|
||||
final BlockingQueue<Message<?>> queue = messageCollector.register(outboundBindTarget);
|
||||
((SubscribableChannel)outboundBindTarget).subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
queue.add(message);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, MessageChannel channel) {
|
||||
messageCollector.unregister(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel outboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, MessageChannel inboundBindTarget) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, MessageChannel requests, MessageChannel replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel bindDynamicProducer(String name, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
return null;
|
||||
public void unbind(Binding<MessageChannel> binding) {
|
||||
if (Binding.Type.producer.equals(binding.getType()))
|
||||
messageCollector.unregister(binding.getTarget());
|
||||
}
|
||||
|
||||
public MessageCollector messageCollector() {
|
||||
@@ -134,7 +87,7 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
|
||||
private final Map<MessageChannel, BlockingQueue<Message<?>>> results = new HashMap<>();
|
||||
|
||||
private BlockingQueue register(MessageChannel channel) {
|
||||
private BlockingQueue<Message<?>> register(MessageChannel channel) {
|
||||
LinkedBlockingDeque<Message<?>> result = new LinkedBlockingDeque<>();
|
||||
Assert.isTrue(!results.containsKey(channel), "Channel [" + channel + "] was already bound");
|
||||
results.put(channel, result);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -288,15 +288,6 @@ public abstract class AbstractBindingPropertiesAccessor {
|
||||
return getProperty(BinderPropertyKeys.PARTITION_INDEX, -1);
|
||||
}
|
||||
|
||||
// Direct Binding
|
||||
|
||||
/**
|
||||
* If true, the binder can attempt a direct binding.
|
||||
*/
|
||||
public boolean isDirectBindingAllowed() {
|
||||
return getProperty(BinderPropertyKeys.DIRECT_BINDING_ALLOWED, false);
|
||||
}
|
||||
|
||||
// Batching
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -32,109 +32,27 @@ import java.util.Properties;
|
||||
public interface Binder<T> {
|
||||
|
||||
/**
|
||||
* Bind a message consumer on a p2p channel
|
||||
* Bind a message consumer on a channel
|
||||
* @param name the logical identity of the message source
|
||||
* @param inboundBindTarget the module interface to be bound as a point to point consumer
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding
|
||||
*/
|
||||
void bindConsumer(String name, T inboundBindTarget, Properties properties);
|
||||
|
||||
/**
|
||||
* Bind a message consumer on a pub/sub channel
|
||||
* @param name the logical identity of the message source
|
||||
* @param inboundBindTarget the module interface to be bound as a pub/sub consumer
|
||||
* @param group the consumer group to which this consumer belongs - subscriptions are shared among consumers
|
||||
* in the same group
|
||||
* in the same group (if <code>null</code> or empty String, the "default" group will be used)
|
||||
* @param inboundBindTarget the module interface to be bound as a consumer
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding
|
||||
*/
|
||||
void bindPubSubConsumer(final String name, T inboundBindTarget, String group, Properties properties);
|
||||
Binding<T> bindConsumer(String name, String group, T inboundBindTarget, Properties properties);
|
||||
|
||||
/**
|
||||
* Bind a message producer on a p2p channel.
|
||||
* Bind a message producer on a channel.
|
||||
* @param name the logical identity of the message target
|
||||
* @param outboundBindTarget the module interface bound as a producer
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding
|
||||
*/
|
||||
void bindProducer(String name, T outboundBindTarget, Properties properties);
|
||||
|
||||
Binding<T> bindProducer(String name, T outboundBindTarget, Properties properties);
|
||||
|
||||
/**
|
||||
* Bind a message producer on a pub/sub channel.
|
||||
* @param name the logical identity of the message target
|
||||
* @param outboundBindTarget the module interface bound as a producer
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding
|
||||
* Unbind the target component represented by the provided Binding and stop any active components.
|
||||
* @param binding the Binding instance to unbind
|
||||
*/
|
||||
void bindPubSubProducer(final String name, T outboundBindTarget, Properties properties);
|
||||
|
||||
/**
|
||||
* Unbind inbound module components and stop any active components that use the channel.
|
||||
* @param name the channel name
|
||||
*/
|
||||
void unbindConsumers(String name);
|
||||
|
||||
/**
|
||||
* Unbind inbound module components and stop any active components that use the channel
|
||||
* with the supplied consumer group.
|
||||
* @param name the channel name
|
||||
* @param group the consumer group
|
||||
*/
|
||||
void unbindPubSubConsumers(String name, String group);
|
||||
|
||||
/**
|
||||
* Unbind outbound module components and stop any active components that use the channel.
|
||||
* @param name the channel name
|
||||
*/
|
||||
void unbindProducers(String name);
|
||||
|
||||
/**
|
||||
* Unbind a specific p2p or pub/sub message consumer
|
||||
* @param name The logical identify of a message source
|
||||
* @param inboundBindTarget The module interface bound as a consumer
|
||||
*/
|
||||
void unbindConsumer(String name, T inboundBindTarget);
|
||||
|
||||
/**
|
||||
* Unbind a specific p2p or pub/sub message producer
|
||||
* @param name the logical identity of the message target
|
||||
* @param outboundBindTarget the channel bound as a producer
|
||||
*/
|
||||
void unbindProducer(String name, T outboundBindTarget);
|
||||
|
||||
/**
|
||||
* Bind a producer that expects async replies. To unbind, invoke unbindProducer() and unbindConsumer().
|
||||
* @param name The name of the requestor.
|
||||
* @param requests The interface used to send requests.
|
||||
* @param replies The interface used to receive replies.
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding.
|
||||
*/
|
||||
void bindRequestor(String name, T requests, T replies, Properties properties);
|
||||
|
||||
/**
|
||||
* Bind a consumer that handles requests from a requestor and asynchronously sends replies. To unbind, invoke
|
||||
* unbindProducer() and unbindConsumer().
|
||||
* @param name The name of the requestor for which this replier will handle requests.
|
||||
* @param requests The interface used to send requests.
|
||||
* @param replies The interface used to receive replies.
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding.
|
||||
*/
|
||||
void bindReplier(String name, T requests, T replies, Properties properties);
|
||||
|
||||
/**
|
||||
* Create an object and bind a producer dynamically, creating the infrastructure
|
||||
* required by the binder technology.
|
||||
* @param name The name of the "queue:" channel.
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding.
|
||||
* @return The bound object.
|
||||
*/
|
||||
T bindDynamicProducer(String name, Properties properties);
|
||||
|
||||
/**
|
||||
* Create an object and bind a producer dynamically, creating the infrastructure
|
||||
* required by the binder technology to broadcast messages to consumers.
|
||||
* @param name The name of the "topic:" channel.
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding.
|
||||
* @return The bound Object.
|
||||
*/
|
||||
T bindDynamicPubSubProducer(String name, Properties properties);
|
||||
void unbind(Binding<T> binding);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
|
||||
/**
|
||||
* Common binder properties.
|
||||
*
|
||||
@@ -92,11 +91,6 @@ public abstract class BinderPropertyKeys {
|
||||
*/
|
||||
public static final String PARTITION_SELECTOR_EXPRESSION = "partitionSelectorExpression";
|
||||
|
||||
/**
|
||||
* If true, the binder will attempt to create a direct binding between the producer and consumer.
|
||||
*/
|
||||
public static final String DIRECT_BINDING_ALLOWED = "directBindingAllowed";
|
||||
|
||||
/**
|
||||
* True if message batching is enabled.
|
||||
*/
|
||||
|
||||
@@ -1,59 +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;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Binder utilities.
|
||||
*
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class BinderUtils {
|
||||
|
||||
/**
|
||||
* The delimiter between a group and index when constructing a binder consumer/producer.
|
||||
*/
|
||||
public static final String GROUP_INDEX_DELIMITER = ".";
|
||||
|
||||
/**
|
||||
* The prefix for the consumer/producer when creating a topic.
|
||||
*/
|
||||
public static final String TOPIC_CHANNEL_PREFIX = "topic:";
|
||||
|
||||
/**
|
||||
* Determine whether the provided channel name represents a pub/sub channel (i.e. topic or tap).
|
||||
* @param channelName name of the channel to check
|
||||
* @return true if pub/sub.
|
||||
*/
|
||||
public static boolean isChannelPubSub(String channelName) {
|
||||
Assert.isTrue(StringUtils.hasText(channelName), "Channel name should not be empty/null.");
|
||||
return channelName.startsWith(TOPIC_CHANNEL_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a name comprised of the group and name.
|
||||
* @param name the name.
|
||||
* @param group the group.
|
||||
* @return the constructed name.
|
||||
*/
|
||||
public static String groupedName(String name, String group) {
|
||||
return group == null ? name : group + BinderUtils.GROUP_INDEX_DELIMITER + name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -18,76 +18,74 @@ package org.springframework.cloud.stream.binder;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents a binding between a module's channel and an adapter endpoint that connects to the Binder. The binding
|
||||
* could be for a consumer or a producer. A consumer binding represents a connection from an adapter on the binder to a
|
||||
* module's input channel. A producer binding represents a connection from a module's output channel to an adapter on
|
||||
* the binder.
|
||||
* Represents a binding between a channel and an adapter endpoint that connects via a Binder. The binding
|
||||
* could be for a consumer or a producer. A consumer binding represents a connection from an adapter to an
|
||||
* input channel. A producer binding represents a connection from an output channel to an adapter.
|
||||
*
|
||||
* @author Jennifer Hickey
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class Binding implements Lifecycle {
|
||||
public class Binding<T> implements Lifecycle {
|
||||
|
||||
public static final String PRODUCER = "producer";
|
||||
|
||||
public static final String CONSUMER = "consumer";
|
||||
|
||||
public static final String DIRECT = "direct";
|
||||
public static enum Type {
|
||||
producer, consumer
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
private final MessageChannel channel;
|
||||
private final String group;
|
||||
|
||||
private final T target;
|
||||
|
||||
private final AbstractEndpoint endpoint;
|
||||
|
||||
private final String type;
|
||||
private final Type type;
|
||||
|
||||
private final AbstractBindingPropertiesAccessor properties;
|
||||
|
||||
private Binding(String name, MessageChannel channel, AbstractEndpoint endpoint, String type,
|
||||
private Binding(String name, String group, T target, AbstractEndpoint endpoint, Type type,
|
||||
AbstractBindingPropertiesAccessor properties) {
|
||||
Assert.notNull(channel, "channel must not be null");
|
||||
Assert.notNull(target, "target must not be null");
|
||||
Assert.notNull(endpoint, "endpoint must not be null");
|
||||
this.name = name;
|
||||
this.channel = channel;
|
||||
this.group = group;
|
||||
this.target = target;
|
||||
this.endpoint = endpoint;
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public static Binding forConsumer(String name, AbstractEndpoint adapterFromBinder, MessageChannel moduleInputChannel,
|
||||
public static <T> Binding<T> forConsumer(String name, String group, AbstractEndpoint adapterFromBinder, T inputTarget,
|
||||
AbstractBindingPropertiesAccessor properties) {
|
||||
return new Binding(name, moduleInputChannel, adapterFromBinder, CONSUMER, properties);
|
||||
return new Binding<T>(name, group, inputTarget, adapterFromBinder, Type.consumer, properties);
|
||||
}
|
||||
|
||||
public static Binding forProducer(String name, MessageChannel moduleOutputChannel, AbstractEndpoint adapterToBinder,
|
||||
public static <T> Binding<T> forProducer(String name, T outputTarget, AbstractEndpoint adapterToBinder,
|
||||
AbstractBindingPropertiesAccessor properties) {
|
||||
return new Binding(name, moduleOutputChannel, adapterToBinder, PRODUCER, properties);
|
||||
}
|
||||
|
||||
public static Binding forDirectProducer(String name, MessageChannel moduleOutputChannel,
|
||||
AbstractEndpoint adapter, AbstractBindingPropertiesAccessor properties) {
|
||||
return new Binding(name, moduleOutputChannel, adapter, DIRECT, properties);
|
||||
return new Binding<T>(name, null, outputTarget, adapterToBinder, Type.producer, properties);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public T getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public AbstractEndpoint getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -112,8 +110,60 @@ public class Binding implements Lifecycle {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + " Binding [name=" + name + ", channel=" + channel + ", endpoint=" + endpoint.getComponentName()
|
||||
return type + " Binding [name=" + name + ", target=" + target + ", endpoint=" + endpoint.getComponentName()
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((endpoint == null) ? 0 : endpoint.hashCode());
|
||||
result = prime * result + ((group == null) ? 0 : group.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((properties == null) ? 0 : properties.hashCode());
|
||||
result = prime * result + ((target == null) ? 0 : target.hashCode());
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Binding<?> other = (Binding<?>) obj;
|
||||
if (endpoint == null) {
|
||||
if (other.endpoint != null)
|
||||
return false;
|
||||
} else if (!endpoint.equals(other.endpoint))
|
||||
return false;
|
||||
if (group == null) {
|
||||
if (other.group != null)
|
||||
return false;
|
||||
} else if (!group.equals(other.group))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (properties == null) {
|
||||
if (other.properties != null)
|
||||
return false;
|
||||
} else if (!properties.equals(other.properties))
|
||||
return false;
|
||||
if (target == null) {
|
||||
if (other.target != null)
|
||||
return false;
|
||||
} else if (!target.equals(other.target))
|
||||
return false;
|
||||
if (type != other.type)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -29,7 +29,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -56,14 +55,12 @@ import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
@@ -78,18 +75,23 @@ import org.springframework.util.StringUtils;
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class MessageChannelBinderSupport
|
||||
implements Binder<MessageChannel>, ApplicationContextAware, InitializingBean {
|
||||
|
||||
protected static final String P2P_NAMED_CHANNEL_TYPE_PREFIX = "queue:";
|
||||
|
||||
protected static final String PUBSUB_NAMED_CHANNEL_TYPE_PREFIX = "topic:";
|
||||
|
||||
protected static final String JOB_CHANNEL_TYPE_PREFIX = "job:";
|
||||
|
||||
protected static final String PARTITION_HEADER = "partition";
|
||||
|
||||
/**
|
||||
* Default group name (used if <code>null</code> or empty String is provided).
|
||||
*/
|
||||
protected static final String DEFAULT_CONSUMER_GROUP = "default";
|
||||
|
||||
/**
|
||||
* The delimiter between a group and index when constructing a binder consumer/producer.
|
||||
*/
|
||||
private static final String GROUP_INDEX_DELIMITER = ".";
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private volatile AbstractApplicationContext applicationContext;
|
||||
@@ -98,8 +100,6 @@ public abstract class MessageChannelBinderSupport
|
||||
|
||||
private final StringConvertingContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver();
|
||||
|
||||
private final ThreadLocal<Boolean> revertingDirectBinding = new ThreadLocal<Boolean>();
|
||||
|
||||
protected static final List<MimeType> MEDIATYPES_MEDIATYPE_ALL = Collections.singletonList(ALL);
|
||||
|
||||
private static final int DEFAULT_BACKOFF_INITIAL_INTERVAL = 1000;
|
||||
@@ -161,7 +161,7 @@ public abstract class MessageChannelBinderSupport
|
||||
BinderPropertyKeys.BATCH_BUFFER_LIMIT,
|
||||
}));
|
||||
|
||||
private final List<Binding> bindings = Collections.synchronizedList(new ArrayList<Binding>());
|
||||
private final List<Binding<MessageChannel>> bindings = Collections.synchronizedList(new ArrayList<Binding<MessageChannel>>());
|
||||
|
||||
private final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
|
||||
|
||||
@@ -206,7 +206,7 @@ public abstract class MessageChannelBinderSupport
|
||||
|
||||
protected volatile boolean defaultCompress = false;
|
||||
|
||||
protected volatile boolean defaultDurableSubscription = false;
|
||||
protected volatile boolean defaultDurableSubscription = true;
|
||||
|
||||
// Payload type cache
|
||||
private volatile Map<String, Class<?>> payloadTypeCache = new ConcurrentHashMap<>();
|
||||
@@ -220,23 +220,6 @@ public abstract class MessageChannelBinderSupport
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* For binder implementations that include a pub/sub component in identifiers, construct the name.
|
||||
* @param name the name.
|
||||
*/
|
||||
public static String applyPubSub(String name) {
|
||||
return "topic." + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the requests entity name.
|
||||
* @param name the name.
|
||||
* @return the request entity name.
|
||||
*/
|
||||
public static String applyRequests(String name) {
|
||||
return name + ".requests";
|
||||
}
|
||||
|
||||
/**
|
||||
* For binder implementations that support dead lettering, construct the name of the dead letter entity for the
|
||||
* underlying pipe name.
|
||||
@@ -383,17 +366,14 @@ public abstract class MessageChannelBinderSupport
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically create a producer for the named channel.
|
||||
* @param name The name.
|
||||
* @param properties The properties.
|
||||
* @return The channel.
|
||||
*/
|
||||
@Override
|
||||
public MessageChannel bindDynamicProducer(String name, Properties properties) {
|
||||
return doBindDynamicProducer(name, name, properties);
|
||||
public final Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) {
|
||||
group = (StringUtils.hasText(group)) ? group : DEFAULT_CONSUMER_GROUP;
|
||||
return doBindConsumer(name, group, inputChannel, properties);
|
||||
}
|
||||
|
||||
protected abstract Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties);
|
||||
|
||||
/**
|
||||
* Create a producer for the named channel and bind it to the binder. Synchronized to avoid creating multiple
|
||||
* instances.
|
||||
@@ -419,44 +399,6 @@ public abstract class MessageChannelBinderSupport
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically create a producer for the named channel. Note: even though it's pub/sub, we still use a direct
|
||||
* channel. It will be bridged to a pub/sub channel in the local binder and bound to an appropriate element for other
|
||||
* binders.
|
||||
* @param name The name.
|
||||
* @param properties The properties.
|
||||
* @return The channel.
|
||||
*/
|
||||
@Override
|
||||
public MessageChannel bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
return doBindDynamicPubSubProducer(name, name, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a producer for the named channel and bind it to the binder. Synchronized to avoid creating multiple
|
||||
* instances.
|
||||
* @param name The name.
|
||||
* @param channelName The name of the channel to be created, and registered as bean.
|
||||
* @param properties The properties.
|
||||
* @return The channel.
|
||||
*/
|
||||
protected synchronized MessageChannel doBindDynamicPubSubProducer(String name, String channelName,
|
||||
Properties properties) {
|
||||
MessageChannel channel = this.directChannelProvider.lookupSharedChannel(channelName);
|
||||
if (channel == null) {
|
||||
try {
|
||||
channel = this.directChannelProvider.createAndRegisterChannel(channelName);
|
||||
bindPubSubProducer(name, channel, properties);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
destroyCreatedChannel(channelName, channel);
|
||||
throw new BinderException(
|
||||
"Failed to bind dynamic channel '" + name + "' with properties " + properties, e);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void destroyCreatedChannel(String name, MessageChannel channel) {
|
||||
BeanFactory beanFactory = this.applicationContext.getBeanFactory();
|
||||
if (beanFactory.containsBean(name)) {
|
||||
@@ -467,82 +409,17 @@ public abstract class MessageChannelBinderSupport
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
deleteBindings("inbound." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
unbindConsumers(BinderUtils.groupedName(name, group));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
deleteBindings("outbound." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, MessageChannel channel) {
|
||||
deleteBinding("inbound." + name, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, MessageChannel channel) {
|
||||
deleteBinding("outbound." + name, channel);
|
||||
}
|
||||
|
||||
protected void addBinding(Binding binding) {
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
protected void deleteBindings(String name) {
|
||||
Assert.hasText(name, "a valid name is required to remove bindings");
|
||||
List<Binding> bindingsToRemove = new ArrayList<Binding>();
|
||||
synchronized (this.bindings) {
|
||||
Iterator<Binding> iterator = this.bindings.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Binding binding = iterator.next();
|
||||
if (binding.getEndpoint().getComponentName().equals(name)) {
|
||||
bindingsToRemove.add(binding);
|
||||
}
|
||||
}
|
||||
for (Binding binding : bindingsToRemove) {
|
||||
doDeleteBinding(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void deleteBinding(String name, MessageChannel channel) {
|
||||
Assert.hasText(name, "a valid name is required to remove a binding");
|
||||
Assert.notNull(channel, "a valid channel is required to remove a binding");
|
||||
Binding bindingToRemove = null;
|
||||
synchronized (this.bindings) {
|
||||
Iterator<Binding> iterator = this.bindings.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Binding binding = iterator.next();
|
||||
if (binding.getChannel().equals(channel) &&
|
||||
binding.getEndpoint().getComponentName().equals(name)) {
|
||||
bindingToRemove = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bindingToRemove != null) {
|
||||
doDeleteBinding(bindingToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void doDeleteBinding(Binding binding) {
|
||||
if (Binding.CONSUMER.equals(binding.getType())) {
|
||||
/*
|
||||
* Revert the direct binding before stopping the consumer; the module
|
||||
* outputChannel will temporarily have 2 subscribers.
|
||||
*/
|
||||
revertDirectBindingIfNecessary(binding);
|
||||
}
|
||||
public void unbind(Binding<MessageChannel> binding) {
|
||||
binding.stop();
|
||||
this.bindings.remove(binding);
|
||||
afterUnbind(binding);
|
||||
}
|
||||
|
||||
protected void afterUnbind(Binding<MessageChannel> binding) {
|
||||
}
|
||||
|
||||
protected void addBinding(Binding<MessageChannel> binding) {
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
protected void stopBindings() {
|
||||
@@ -558,6 +435,19 @@ public abstract class MessageChannelBinderSupport
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a name comprised of the name and group.
|
||||
* @param name the name.
|
||||
* @param group the group.
|
||||
* @return the constructed name.
|
||||
*/
|
||||
protected final String groupedName(String name, String group) {
|
||||
if (!StringUtils.hasText(group)) {
|
||||
group = "default";
|
||||
}
|
||||
return name + GROUP_INDEX_DELIMITER + group;
|
||||
}
|
||||
|
||||
protected final MessageValues serializePayloadIfNecessary(Message<?> message) {
|
||||
Object originalPayload = message.getPayload();
|
||||
Object originalContentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
|
||||
@@ -821,125 +711,6 @@ public abstract class MessageChannelBinderSupport
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isNamedChannel(String name) {
|
||||
return name.startsWith(PUBSUB_NAMED_CHANNEL_TYPE_PREFIX) || name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX)
|
||||
|| name.startsWith(JOB_CHANNEL_TYPE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create a direct binding (avoiding the broker) if the consumer is local. Named channel producers are not
|
||||
* bound directly.
|
||||
* @param name The name.
|
||||
* @param moduleOutputChannel The channel to bind.
|
||||
* @param properties The producer properties.
|
||||
* @return true if the producer is bound.
|
||||
*/
|
||||
protected boolean bindNewProducerDirectlyIfPossible(String name, SubscribableChannel moduleOutputChannel,
|
||||
AbstractBindingPropertiesAccessor properties) {
|
||||
if (!properties.isDirectBindingAllowed()) {
|
||||
return false;
|
||||
}
|
||||
else if (isNamedChannel(name)) {
|
||||
return false;
|
||||
}
|
||||
else if (this.revertingDirectBinding.get() != null) {
|
||||
// we're in the process of unbinding a direct binding
|
||||
this.revertingDirectBinding.remove();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
Binding consumerBinding = null;
|
||||
synchronized (this.bindings) {
|
||||
for (Binding binding : this.bindings) {
|
||||
if (binding.getName().equals(name) && Binding.CONSUMER.equals(binding.getType())) {
|
||||
consumerBinding = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (consumerBinding == null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
bindProducerDirectly(name, moduleOutputChannel, consumerBinding.getChannel(), properties);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindProducerDirectly(String name, SubscribableChannel producerChannel,
|
||||
MessageChannel consumerChannel, AbstractBindingPropertiesAccessor properties) {
|
||||
DirectHandler handler = new DirectHandler(consumerChannel);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer(producerChannel, handler);
|
||||
consumer.setBeanFactory(getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
Binding binding = Binding.forDirectProducer(name, producerChannel, consumer, properties);
|
||||
addBinding(binding);
|
||||
binding.start();
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("Producer bound directly: " + binding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to bind a producer directly (avoiding the broker) if there is already a local producer. PubSub producers
|
||||
* cannot be bound directly. Create the direct binding, then unbind the existing producer.
|
||||
* @param name The name.
|
||||
* @param consumerChannel The channel to bind the producer to.
|
||||
*/
|
||||
protected void bindExistingProducerDirectlyIfPossible(String name, MessageChannel consumerChannel) {
|
||||
if (!isNamedChannel(name)) {
|
||||
Binding producerBinding = null;
|
||||
synchronized (this.bindings) {
|
||||
for (Binding binding : this.bindings) {
|
||||
if (binding.getName().equals(name) && Binding.PRODUCER.equals(binding.getType())) {
|
||||
producerBinding = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (producerBinding != null && producerBinding.getChannel() instanceof SubscribableChannel) {
|
||||
AbstractBindingPropertiesAccessor properties = producerBinding.getPropertiesAccessor();
|
||||
if (properties.isDirectBindingAllowed()) {
|
||||
bindProducerDirectly(name, (SubscribableChannel) producerBinding.getChannel(), consumerChannel,
|
||||
properties);
|
||||
producerBinding.stop();
|
||||
this.bindings.remove(producerBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void revertDirectBindingIfNecessary(Binding binding) {
|
||||
try {
|
||||
synchronized (this.bindings) { // Not necessary, called while synchronized, but just in case...
|
||||
Binding directBinding = null;
|
||||
Iterator<Binding> iterator = this.bindings.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Binding producer = iterator.next();
|
||||
if (Binding.DIRECT.equals(producer.getType()) && binding.getName().equals(producer.getName())) {
|
||||
this.revertingDirectBinding.set(Boolean.TRUE);
|
||||
bindProducer(producer.getName(), producer.getChannel(),
|
||||
producer.getPropertiesAccessor().getProperties());
|
||||
directBinding = producer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (directBinding != null) {
|
||||
directBinding.stop();
|
||||
this.bindings.remove(directBinding);
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("direct binding reverted: " + directBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.logger.error("Could not revert direct binding: " + binding, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default partition strategy; only works on keys with "real" hash codes, such as String. Caller now always applies
|
||||
* modulo so no need to do so here.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -18,15 +18,21 @@ package org.springframework.cloud.stream.binding;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver;
|
||||
import org.springframework.messaging.core.DestinationResolutionException;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.messaging.core.DestinationResolver} implementation that first checks for any channel
|
||||
* whose name begins with a colon in the {@link Binder}.
|
||||
* A {@link org.springframework.messaging.core.DestinationResolver} implementation that
|
||||
* 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
|
||||
*/
|
||||
@@ -36,11 +42,21 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
|
||||
|
||||
private final Properties producerProperties;
|
||||
|
||||
private DefaultListableBeanFactory beanFactory;
|
||||
|
||||
public BinderAwareChannelResolver(BinderFactory<MessageChannel> binderFactory, Properties producerProperties) {
|
||||
this.binderFactory = binderFactory;
|
||||
this.producerProperties = producerProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
super.setBeanFactory(beanFactory);
|
||||
if (beanFactory instanceof ConfigurableBeanFactory) {
|
||||
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel resolveDestination(String name) {
|
||||
MessageChannel channel = null;
|
||||
@@ -49,38 +65,32 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
|
||||
}
|
||||
catch (DestinationResolutionException e) {
|
||||
}
|
||||
if (name.contains(":")) {
|
||||
if (binderFactory != null) {
|
||||
String[] tokens = name.split(":", 2);
|
||||
synchronized (this) {
|
||||
try {
|
||||
return super.resolveDestination(name);
|
||||
}
|
||||
catch (DestinationResolutionException e) {
|
||||
}
|
||||
if (this.beanFactory != null && this.binderFactory != null) {
|
||||
channel = new DirectChannel();
|
||||
this.beanFactory.registerSingleton(name, channel);
|
||||
channel = (MessageChannel) this.beanFactory.initializeBean(channel, name);
|
||||
String transport = null;
|
||||
String type;
|
||||
if (tokens.length == 2) {
|
||||
type = tokens[0];
|
||||
}
|
||||
else if (tokens.length == 3) {
|
||||
transport = tokens[0];
|
||||
type = tokens[1];
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unrecognized channel naming scheme: " + name + " , should be" +
|
||||
" [<transport>:]<type>:<name>");
|
||||
if (name.contains(":")) {
|
||||
String[] tokens = name.split(":", 2);
|
||||
if (tokens.length == 2) {
|
||||
transport = tokens[0];
|
||||
}
|
||||
else if (tokens.length != 1) {
|
||||
throw new IllegalArgumentException("Unrecognized channel naming scheme: " + name + " , should be" +
|
||||
" [<transport>:]<name>");
|
||||
}
|
||||
}
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder(transport);
|
||||
if ("queue".equals(type)) {
|
||||
channel = binder.bindDynamicProducer(name, this.producerProperties);
|
||||
}
|
||||
else if ("topic".equals(type)) {
|
||||
channel = binder.bindDynamicPubSubProducer(name, this.producerProperties);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("unrecognized channel type: " + type);
|
||||
}
|
||||
binder.bindProducer(name, channel, this.producerProperties);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
if (channel == null) {
|
||||
channel = super.resolveDestination(name);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
package org.springframework.cloud.stream.binding;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Handles the operations related to channel binding including binding of input/output channels by delegating
|
||||
@@ -41,57 +42,42 @@ public class ChannelBindingService {
|
||||
|
||||
private final ChannelBindingServiceProperties channelBindingServiceProperties;
|
||||
|
||||
private final Map<String, Binding<MessageChannel>> producerBindings = new HashMap<>();
|
||||
|
||||
private final Map<String, Binding<MessageChannel>> consumerBindings = new HashMap<>();
|
||||
|
||||
public ChannelBindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
|
||||
BinderFactory<MessageChannel> binderFactory) {
|
||||
this.channelBindingServiceProperties = channelBindingServiceProperties;
|
||||
this.binderFactory = binderFactory;
|
||||
}
|
||||
|
||||
public void bindConsumer(MessageChannel inputChannel, String inputChannelName) {
|
||||
public Binding<MessageChannel> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName);
|
||||
Binder<MessageChannel> binder = getBinderForChannel(inputChannelName);
|
||||
if (BinderUtils.isChannelPubSub(channelBindingTarget)) {
|
||||
binder.bindPubSubConsumer(removePrefix(channelBindingTarget),
|
||||
inputChannel, consumerGroup(inputChannelName),
|
||||
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName));
|
||||
}
|
||||
else {
|
||||
binder.bindConsumer(channelBindingTarget, inputChannel,
|
||||
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName));
|
||||
}
|
||||
Binding<MessageChannel> binding = binder.bindConsumer(channelBindingTarget, consumerGroup(inputChannelName), inputChannel,
|
||||
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName));
|
||||
this.consumerBindings.put(inputChannelName, binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
public void bindProducer(MessageChannel outputChannel, String outputChannelName) {
|
||||
public Binding<MessageChannel> bindProducer(MessageChannel outputChannel, String outputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName);
|
||||
Binder<MessageChannel> binder = getBinderForChannel(outputChannelName);
|
||||
if (BinderUtils.isChannelPubSub(channelBindingTarget)) {
|
||||
binder.bindPubSubProducer(removePrefix(channelBindingTarget),
|
||||
outputChannel, this.channelBindingServiceProperties.getProducerProperties(outputChannelName));
|
||||
}
|
||||
else {
|
||||
binder.bindProducer(channelBindingTarget, outputChannel,
|
||||
this.channelBindingServiceProperties.getProducerProperties(outputChannelName));
|
||||
}
|
||||
}
|
||||
|
||||
private String removePrefix(String bindingTarget) {
|
||||
Assert.isTrue(StringUtils.hasText(bindingTarget), "Binding target should not be empty/null.");
|
||||
return bindingTarget.substring(bindingTarget.indexOf(":") + 1);
|
||||
Binding<MessageChannel> binding = binder.bindProducer(channelBindingTarget, outputChannel,
|
||||
this.channelBindingServiceProperties.getProducerProperties(outputChannelName));
|
||||
this.producerBindings.put(outputChannelName, binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
public void unbindConsumers(String inputChannelName) {
|
||||
Binder<MessageChannel> binder = getBinderForChannel(inputChannelName);
|
||||
if (BinderUtils.isChannelPubSub(this.channelBindingServiceProperties.getBindingDestination(inputChannelName))) {
|
||||
binder.unbindPubSubConsumers(inputChannelName, consumerGroup(inputChannelName));
|
||||
}
|
||||
else {
|
||||
binder.unbindConsumers(inputChannelName);
|
||||
}
|
||||
binder.unbind(this.consumerBindings.remove(inputChannelName));
|
||||
}
|
||||
|
||||
public void unbindProducers(String outputChannelName) {
|
||||
Binder<MessageChannel> binder = getBinderForChannel(outputChannelName);
|
||||
binder.unbindProducers(outputChannelName);
|
||||
binder.unbind(this.producerBindings.remove(outputChannelName));
|
||||
}
|
||||
|
||||
private Binder<MessageChannel> getBinderForChannel(String channelName) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -54,8 +55,8 @@ public class ArbitraryInterfaceBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testArbitraryInterfaceChannelsBound() {
|
||||
verify(binder).bindConsumer(eq("someQueue.0"), eq(fooChannels.foo()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("someQueue.1"), eq(fooChannels.bar()), Mockito.<Properties>any());
|
||||
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());
|
||||
verifyNoMoreInteractions(binder);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -53,8 +54,8 @@ public class ArbitraryInterfaceBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testArbitraryInterfaceChannelsBound() {
|
||||
verify(binder).bindConsumer(eq("foo"), eq(fooChannels.foo()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("bar"), eq(fooChannels.bar()), Mockito.<Properties>any());
|
||||
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());
|
||||
verifyNoMoreInteractions(binder);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -20,8 +20,9 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -41,7 +42,6 @@ import org.springframework.cloud.stream.binder.local.LocalMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.integration.scheduling.PollerMetadata;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -76,7 +76,7 @@ public class BinderAwareChannelResolverTests {
|
||||
return binder;
|
||||
}
|
||||
}, null);
|
||||
this.resolver.setBeanFactory(context);
|
||||
this.resolver.setBeanFactory(context.getBeanFactory());
|
||||
context.getBeanFactory().registerSingleton("channelResolver",
|
||||
this.resolver);
|
||||
context.registerSingleton("other", DirectChannel.class);
|
||||
@@ -91,8 +91,8 @@ public class BinderAwareChannelResolverTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveQueueChannel() {
|
||||
MessageChannel registered = resolver.resolveDestination("queue:foo");
|
||||
public void resolveChannel() {
|
||||
MessageChannel registered = resolver.resolveDestination("foo");
|
||||
DirectChannel testChannel = new DirectChannel();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final List<Message<?>> received = new ArrayList<Message<?>>();
|
||||
@@ -104,7 +104,7 @@ public class BinderAwareChannelResolverTests {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
binder.bindConsumer("queue:foo", testChannel, null);
|
||||
binder.bindConsumer("foo", null, testChannel, null);
|
||||
assertEquals(0, received.size());
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
try {
|
||||
@@ -119,41 +119,6 @@ public class BinderAwareChannelResolverTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveTopicChannel() {
|
||||
MessageChannel registered = resolver.resolveDestination("topic:bar");
|
||||
PublishSubscribeChannel[] testChannels = {
|
||||
new PublishSubscribeChannel(), new PublishSubscribeChannel(), new PublishSubscribeChannel()
|
||||
};
|
||||
final CountDownLatch latch = new CountDownLatch(testChannels.length);
|
||||
final List<Message<?>> received = new ArrayList<Message<?>>();
|
||||
for (PublishSubscribeChannel testChannel : testChannels) {
|
||||
testChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
received.add(message);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
binder.bindPubSubConsumer("topic:bar", testChannel, null, null);
|
||||
}
|
||||
assertEquals(0, received.size());
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
try {
|
||||
assertTrue("latch timed out", latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
fail("interrupted while awaiting latch");
|
||||
}
|
||||
assertEquals(3, received.size());
|
||||
assertEquals("hello", received.get(0).getPayload());
|
||||
assertEquals("hello", received.get(1).getPayload());
|
||||
assertEquals("hello", received.get(2).getPayload());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNonRegisteredChannel() {
|
||||
MessageChannel other = resolver.resolveDestination("other");
|
||||
@@ -161,12 +126,11 @@ public class BinderAwareChannelResolverTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void propertyPassthrough() {
|
||||
Properties properties = new Properties();
|
||||
@SuppressWarnings("rawtypes")
|
||||
Binder binderFactory = mock(Binder.class);
|
||||
doReturn(new DirectChannel()).when(binderFactory).bindDynamicProducer("queue:foo", properties);
|
||||
doReturn(new DirectChannel()).when(binderFactory).bindDynamicPubSubProducer("topic:bar", properties);
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel> binderFactory = mock(Binder.class);
|
||||
BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
|
||||
Mockito.when(mockBinderFactory.getBinder(anyString())).thenReturn(binderFactory);
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -174,10 +138,12 @@ public class BinderAwareChannelResolverTests {
|
||||
new BinderAwareChannelResolver(mockBinderFactory, properties);
|
||||
BeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
resolver.setBeanFactory(beanFactory);
|
||||
resolver.resolveDestination("queue:foo");
|
||||
resolver.resolveDestination("topic:bar");
|
||||
verify(binderFactory).bindDynamicProducer("queue:foo", properties);
|
||||
verify(binderFactory).bindDynamicPubSubProducer("topic:bar", properties);
|
||||
MessageChannel resolved = resolver.resolveDestination("foo");
|
||||
verify(binderFactory).bindProducer(eq("foo"), any(MessageChannel.class), eq(properties));
|
||||
assertSame(resolved, beanFactory.getBean("foo"));
|
||||
resolved = resolver.resolveDestination("someTransport:foo");
|
||||
verify(binderFactory).bindProducer(eq("someTransport:foo"), any(MessageChannel.class), eq(properties));
|
||||
assertSame(resolved, beanFactory.getBean("someTransport:foo"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -51,7 +52,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"), eq(processor.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.<Properties>any());
|
||||
SomeLifecycle someLifecycle = applicationContext.getBean(SomeLifecycle.class);
|
||||
assertTrue(someLifecycle.isRunning());
|
||||
applicationContext.close();
|
||||
@@ -81,6 +82,7 @@ public class InputOutputBindingOrderTest {
|
||||
private Processor processor;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void start() {
|
||||
verify(this.binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.<Properties>any());
|
||||
// input was not bound yet
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -53,7 +54,7 @@ public class ProcessorBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("testtock.0"), eq(testProcessor.input()), Mockito.<Properties>any());
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
@@ -52,7 +53,7 @@ public class ProcessorBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), eq(processor.input()), Mockito.<Properties>any());
|
||||
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());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@@ -1,71 +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;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(ProcessorBindingTestsWithPubSubBindingTargets.TestProcessor.class)
|
||||
public class ProcessorBindingTestsWithPubSubBindingTargets {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Autowired
|
||||
private Binder binder;
|
||||
|
||||
@Autowired @Bindings(TestProcessor.class)
|
||||
private Processor testProcessor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindPubSubConsumer(eq("testtock.0"), eq(testProcessor.input()), anyString(),
|
||||
any(Properties.class));
|
||||
verify(binder).bindPubSubProducer(eq("testtock.1"), eq(testProcessor.output()), any(Properties.class));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
@Import(MockBinderRegistryConfiguration.class)
|
||||
@PropertySource("classpath:/org/springframework/cloud/stream/binder/processor-binding-test-pubsub.properties")
|
||||
public static class TestProcessor {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,70 +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;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Sink;
|
||||
import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(SinkBindingPubSubTests.TestSink.class)
|
||||
public class SinkBindingPubSubTests {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Autowired
|
||||
private Binder binder;
|
||||
|
||||
@Autowired @Bindings(TestSink.class)
|
||||
private Sink testSink;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindPubSubConsumer(eq("testpubsub"), eq(testSink.input()), eq("tgroup"), any(Properties.class));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
@Import(MockBinderRegistryConfiguration.class)
|
||||
@PropertySource("classpath:/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties")
|
||||
public static class TestSink {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -54,7 +55,7 @@ public class SinkBindingTestsWithBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("testtock"), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("testtock"), anyString(), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -53,7 +54,7 @@ public class SinkBindingTestsWithDefaults {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindConsumer(eq("input"), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindConsumer(eq("input"), anyString(), eq(testSink.input()), Mockito.<Properties>any());
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-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.
|
||||
@@ -17,18 +17,12 @@
|
||||
package org.springframework.cloud.stream.binder.local;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBindingPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderPropertyKeys;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.ExecutorChannel;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
|
||||
@@ -37,10 +31,7 @@ import org.springframework.integration.scheduling.PollerMetadata;
|
||||
import org.springframework.integration.support.context.NamedComponent;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
@@ -60,6 +51,8 @@ import org.springframework.util.MimeType;
|
||||
*/
|
||||
public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
public static final String THREAD_NAME_PREFIX = "binder.local-";
|
||||
|
||||
private static final int DEFAULT_EXECUTOR_CORE_POOL_SIZE = 0;
|
||||
|
||||
private static final int DEFAULT_EXECUTOR_MAX_POOL_SIZE = 200;
|
||||
@@ -68,19 +61,8 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
private static final int DEFAULT_EXECUTOR_KEEPALIVE_SECONDS = 60;
|
||||
|
||||
private static final int DEFAULT_REQ_REPLY_CONCURRENCY = 1;
|
||||
|
||||
protected static final Set<Object> CONSUMER_REQUEST_REPLY_PROPERTIES = new SetBuilder()
|
||||
.addAll(CONSUMER_STANDARD_PROPERTIES)
|
||||
.add(BinderPropertyKeys.CONCURRENCY)
|
||||
.build();
|
||||
|
||||
public static final String THREAD_NAME_PREFIX = "binder.local-";
|
||||
|
||||
private volatile PollerMetadata poller;
|
||||
|
||||
private final Map<String, ExecutorChannel> requestReplyChannels = new HashMap<String, ExecutorChannel>();
|
||||
|
||||
private final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
private volatile int executorCorePoolSize = DEFAULT_EXECUTOR_CORE_POOL_SIZE;
|
||||
@@ -91,23 +73,6 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
private volatile int executorKeepAliveSeconds = DEFAULT_EXECUTOR_KEEPALIVE_SECONDS;
|
||||
|
||||
private volatile int queueSize = Integer.MAX_VALUE;
|
||||
|
||||
private final Map<String, ThreadPoolTaskExecutor> reqRepExecutors = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Used to create and customize {@link QueueChannel}s when the binding operation involves aliased names.
|
||||
*/
|
||||
private final SharedChannelProvider<QueueChannel> queueChannelProvider = new SharedChannelProvider<QueueChannel>(
|
||||
QueueChannel.class) {
|
||||
|
||||
@Override
|
||||
protected QueueChannel createSharedChannel(String name) {
|
||||
QueueChannel queueChannel = new QueueChannel(queueSize);
|
||||
return queueChannel;
|
||||
}
|
||||
};
|
||||
|
||||
private final SharedChannelProvider<PublishSubscribeChannel> pubsubChannelProvider = new SharedChannelProvider<PublishSubscribeChannel>(
|
||||
PublishSubscribeChannel.class) {
|
||||
|
||||
@@ -126,13 +91,6 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
this.poller = poller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the queue when using {@link QueueChannel}s.
|
||||
*/
|
||||
public void setQueueSize(int queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ThreadPoolTaskExecutor}} core pool size to limit the number of concurrent
|
||||
* threads. The executor is used for PubSub operations.
|
||||
@@ -183,61 +141,23 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
this.executor.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* For the local binder we bridge the router "output" channel to a queue channel; the queue
|
||||
* channel gets the name and the source channel is named 'dynamic.output.to.' + name.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MessageChannel bindDynamicProducer(String name, Properties properties) {
|
||||
return doBindDynamicProducer(name, "dynamic.output.to." + name, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the local binder we bridge the router "output" channel to a pub/sub channel; the pub/sub
|
||||
* channel gets the name and the source channel is named 'dynamic.output.to.' + name.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MessageChannel bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
return doBindDynamicPubSubProducer(name, "dynamic.output.to." + name, properties);
|
||||
}
|
||||
|
||||
private SharedChannelProvider<?> getChannelProvider(String name) {
|
||||
SharedChannelProvider<?> channelProvider = directChannelProvider;
|
||||
// Use queue channel provider in case of named channels:
|
||||
// point-to-point type syntax (queue:) and job input channel syntax (job:)
|
||||
if (name.startsWith(P2P_NAMED_CHANNEL_TYPE_PREFIX) || name.startsWith(JOB_CHANNEL_TYPE_PREFIX)) {
|
||||
channelProvider = queueChannelProvider;
|
||||
}
|
||||
return channelProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up or creates a DirectChannel with the given name and creates a bridge from that channel to the provided
|
||||
* channel instance.
|
||||
*/
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
validateConsumerProperties(name, properties, CONSUMER_STANDARD_PROPERTIES);
|
||||
doRegisterConsumer(name, moduleInputChannel, getChannelProvider(name), properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, String group,
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel moduleInputChannel,
|
||||
Properties properties) {
|
||||
validateConsumerProperties(name, properties, CONSUMER_STANDARD_PROPERTIES);
|
||||
doRegisterConsumer(name, moduleInputChannel, this.pubsubChannelProvider, properties);
|
||||
return doRegisterConsumer(name, moduleInputChannel, this.pubsubChannelProvider, properties);
|
||||
}
|
||||
|
||||
private void doRegisterConsumer(String name, MessageChannel moduleInputChannel,
|
||||
private Binding<MessageChannel> doRegisterConsumer(String name, MessageChannel moduleInputChannel,
|
||||
SharedChannelProvider<?> channelProvider, Properties properties) {
|
||||
Assert.hasText(name, "a valid name is required to register an inbound channel");
|
||||
Assert.notNull(moduleInputChannel, "channel must not be null");
|
||||
MessageChannel registeredChannel = channelProvider.lookupOrCreateSharedChannel(name);
|
||||
MessageChannel registeredChannel = channelProvider.lookupOrCreateSharedChannel("localbinder." + name);
|
||||
bridge(name, registeredChannel, moduleInputChannel,
|
||||
"inbound." + ((NamedComponent) registeredChannel).getComponentName(),
|
||||
new LocalBindingPropertiesAccessor(properties));
|
||||
// TODO: ?
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,109 +165,25 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
* channel instance.
|
||||
*/
|
||||
@Override
|
||||
public void bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
validateConsumerProperties(name, properties, PRODUCER_STANDARD_PROPERTIES);
|
||||
doRegisterProducer(name, moduleOutputChannel, getChannelProvider(name), properties);
|
||||
return doRegisterProducer(name, moduleOutputChannel, this.pubsubChannelProvider, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, MessageChannel moduleOutputChannel,
|
||||
Properties properties) {
|
||||
validateConsumerProperties(name, properties, PRODUCER_STANDARD_PROPERTIES);
|
||||
doRegisterProducer(name, moduleOutputChannel, this.pubsubChannelProvider, properties);
|
||||
}
|
||||
|
||||
private void doRegisterProducer(String name, MessageChannel moduleOutputChannel,
|
||||
private Binding<MessageChannel> doRegisterProducer(String name, MessageChannel moduleOutputChannel,
|
||||
SharedChannelProvider<?> channelProvider, Properties properties) {
|
||||
Assert.hasText(name, "a valid name is required to register an outbound channel");
|
||||
Assert.notNull(moduleOutputChannel, "channel must not be null");
|
||||
MessageChannel registeredChannel = channelProvider.lookupOrCreateSharedChannel(name);
|
||||
MessageChannel registeredChannel = channelProvider.lookupOrCreateSharedChannel("localbinder." + name);
|
||||
bridge(name, moduleOutputChannel, registeredChannel,
|
||||
"outbound." + ((NamedComponent) registeredChannel).getComponentName(),
|
||||
new LocalBindingPropertiesAccessor(properties));
|
||||
// TODO: ?
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(final String name, MessageChannel requests, final MessageChannel replies,
|
||||
Properties properties) {
|
||||
validateConsumerProperties(name, properties, CONSUMER_REQUEST_REPLY_PROPERTIES);
|
||||
final MessageChannel requestChannel = this.findOrCreateRequestReplyChannel(name, "requestor.", properties);
|
||||
Assert.isInstanceOf(SubscribableChannel.class, requests);
|
||||
((SubscribableChannel) requests).subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
requestChannel.send(message);
|
||||
}
|
||||
});
|
||||
|
||||
ExecutorChannel replyChannel = this.findOrCreateRequestReplyChannel(name, "replier.", properties);
|
||||
replyChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
replies.send(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, final MessageChannel requests, MessageChannel replies,
|
||||
Properties properties) {
|
||||
validateConsumerProperties(name, properties, CONSUMER_REQUEST_REPLY_PROPERTIES);
|
||||
SubscribableChannel requestChannel = this.findOrCreateRequestReplyChannel(name, "requestor.", properties);
|
||||
requestChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
requests.send(message);
|
||||
}
|
||||
});
|
||||
|
||||
Assert.isInstanceOf(SubscribableChannel.class, replies);
|
||||
final SubscribableChannel replyChannel = this.findOrCreateRequestReplyChannel(name, "replier.", properties);
|
||||
((SubscribableChannel) replies).subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
replyChannel.send(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized ExecutorChannel findOrCreateRequestReplyChannel(String name, String prefix,
|
||||
Properties properties) {
|
||||
String channelName = prefix + name;
|
||||
ExecutorChannel channel = this.requestReplyChannels.get(channelName);
|
||||
if (channel == null) {
|
||||
ThreadPoolTaskExecutor executor = createRequestReplyExecutor(name, properties);
|
||||
channel = new ExecutorChannel(executor);
|
||||
channel.setBeanFactory(getBeanFactory());
|
||||
this.requestReplyChannels.put(channelName, channel);
|
||||
this.reqRepExecutors.put(name, executor);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
private ThreadPoolTaskExecutor createRequestReplyExecutor(String name, Properties properties) {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(new LocalBindingPropertiesAccessor(properties).getConcurrency(DEFAULT_REQ_REPLY_CONCURRENCY));
|
||||
executor.setThreadNamePrefix(THREAD_NAME_PREFIX + name + "-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, MessageChannel channel) {
|
||||
this.requestReplyChannels.remove("replier." + name);
|
||||
MessageChannel requestChannel = this.requestReplyChannels.remove("requestor." + name);
|
||||
if (requestChannel == null) {
|
||||
super.unbindProducer(name, channel);
|
||||
}
|
||||
ThreadPoolTaskExecutor executor = this.reqRepExecutors.remove(name);
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
public void unbind(Binding<MessageChannel> binding) {
|
||||
}
|
||||
|
||||
protected BridgeHandler bridge(String name, MessageChannel from, MessageChannel to, String bridgeName,
|
||||
@@ -397,7 +233,7 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
try {
|
||||
cefb.getObject().setComponentName(handler.getComponentName());
|
||||
Binding binding = isInbound ? Binding.forConsumer(name, cefb.getObject(), to, properties)
|
||||
Binding<MessageChannel> binding = isInbound ? Binding.forConsumer(name, null, cefb.getObject(), to, properties)
|
||||
: Binding.forProducer(name, from, cefb.getObject(), properties);
|
||||
addBinding(binding);
|
||||
binding.start();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -19,11 +19,13 @@ 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;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class StubBinder1 implements Binder {
|
||||
public class StubBinder1 implements Binder<Object> {
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -36,67 +38,17 @@ public class StubBinder1 implements Binder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, Object inboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, Object inboundBindTarget, String group, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, Object inboundBindTarget) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, Object outboundBindTarget) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, Object requests, Object replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, Object requests, Object replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object bindDynamicProducer(String name, Properties properties) {
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind(Binding<Object> binding) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -31,7 +31,7 @@ public class StubBinder1Configuration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("binder1")
|
||||
public Binder binder() {
|
||||
public Binder<?> binder() {
|
||||
return new StubBinder1();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -19,80 +19,33 @@ 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;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class StubBinder2 implements Binder {
|
||||
public class StubBinder2 implements Binder<Object> {
|
||||
|
||||
private StubBinder2Dependency stubBinder2Dependency;
|
||||
@SuppressWarnings("unused")
|
||||
private final StubBinder2Dependency stubBinder2Dependency;
|
||||
|
||||
public StubBinder2(StubBinder2Dependency stubBinder2Dependency) {
|
||||
this.stubBinder2Dependency = stubBinder2Dependency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, Object inboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, Object inboundBindTarget, String group, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindConsumer(String name, Object inboundBindTarget) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducer(String name, Object outboundBindTarget) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRequestor(String name, Object requests, Object replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindReplier(String name, Object requests, Object replies, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object bindDynamicProducer(String name, Properties properties) {
|
||||
public Binding<Object> bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object bindDynamicPubSubProducer(String name, Properties properties) {
|
||||
public Binding<Object> bindProducer(String name, Object outboundBindTarget, Properties properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind(Binding<Object> binding) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -28,7 +28,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.stream.binding;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -27,6 +28,7 @@ import org.junit.Test;
|
||||
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.DefaultBinderFactory;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
@@ -36,12 +38,12 @@ import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class ChannelBindingServiceTests {
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
public void testDefaultGroup() throws Exception {
|
||||
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
|
||||
Map<String, BindingProperties> bindings = new HashMap<>();
|
||||
BindingProperties props = new BindingProperties();
|
||||
@@ -49,7 +51,6 @@ public class ChannelBindingServiceTests {
|
||||
String name = "foo";
|
||||
bindings.put(name, props);
|
||||
properties.setBindings(bindings);
|
||||
@SuppressWarnings("unchecked")
|
||||
DefaultBinderFactory<MessageChannel> binderFactory =
|
||||
new DefaultBinderFactory<>(Collections.singletonMap("mock",
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
@@ -57,23 +58,22 @@ public class ChannelBindingServiceTests {
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
service.bindConsumer(inputChannel, name);
|
||||
Binding<MessageChannel> binding = service.bindConsumer(inputChannel, name);
|
||||
service.unbindConsumers(name);
|
||||
verify(binder).bindConsumer(name, inputChannel, properties.getConsumerProperties(name));
|
||||
verify(binder).unbindConsumers(name);
|
||||
verify(binder).bindConsumer(name, props.getGroup(), inputChannel, properties.getConsumerProperties(name));
|
||||
verify(binder).unbind(binding);
|
||||
binderFactory.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPubSub() throws Exception {
|
||||
public void testExplicitGroup() throws Exception {
|
||||
ChannelBindingServiceProperties properties = new ChannelBindingServiceProperties();
|
||||
Map<String, BindingProperties> bindings = new HashMap<>();
|
||||
BindingProperties props = new BindingProperties();
|
||||
props.setDestination("topic:foo");
|
||||
props.setDestination("foo");
|
||||
String name = "foo";
|
||||
bindings.put(name, props);
|
||||
properties.setBindings(bindings);
|
||||
@SuppressWarnings("unchecked")
|
||||
DefaultBinderFactory<MessageChannel> binderFactory =
|
||||
new DefaultBinderFactory<>(Collections.singletonMap("mock",
|
||||
new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}),
|
||||
@@ -81,10 +81,10 @@ public class ChannelBindingServiceTests {
|
||||
Binder<MessageChannel> binder = binderFactory.getBinder("mock");
|
||||
ChannelBindingService service = new ChannelBindingService(properties, binderFactory);
|
||||
MessageChannel inputChannel = new DirectChannel();
|
||||
service.bindConsumer(inputChannel, name);
|
||||
Binding<MessageChannel> binding = service.bindConsumer(inputChannel, name);
|
||||
service.unbindConsumers(name);
|
||||
verify(binder).bindPubSubConsumer(name, inputChannel, props.getGroup(), properties.getConsumerProperties(name));
|
||||
verify(binder).unbindPubSubConsumers(name, props.getGroup());
|
||||
verify(binder).bindConsumer(name, props.getGroup(), inputChannel, properties.getConsumerProperties(name));
|
||||
verify(binder).unbind(binding);
|
||||
binderFactory.destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.stream.partitioning;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -61,10 +62,9 @@ public class PartitionedConsumerTest {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testBindingPartitionedConsumer() {
|
||||
ArgumentCaptor<Properties> argumentCaptor = ArgumentCaptor.forClass(Properties.class);
|
||||
verify(binder).bindConsumer(eq("partIn"), eq(testSink.input()), argumentCaptor.capture());
|
||||
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().getProperty(BinderPropertyKeys.COUNT), equalTo("2"));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user