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:
Mark Fisher
2015-12-21 15:48:48 -05:00
committed by Marius Bogoevici
parent 60e44b530d
commit c3758b9dc0
46 changed files with 1052 additions and 2758 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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