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