XD-3519: Rabbit Taps
- instead of publishing to the default exchange, producers now publish to a topic exchange, to which the main queue is bound
- taps on a stream simply bind a queue to the same exchange with a name including the group for the tapping stream and a
routing key pattern `#`
- this means that multiple instances of the same tap stream will compete but multiple tap streams will get a copy
- for partitioned data, a single topic exchange is used, which each partition queue bound with its name as the routing key
- this means taps on partitioned data see the consolidated messages
Remove unused binder utilities.
Use TopicExchange Always
Use a TopicExchange when binding a pubSub producer for consistency.
Remove Deprecated Binder Method
Add group to BindingProperties
- also fix artifact name in test support pom
- also fix some kafka tests (does not imply that kafka taps work - yet)
This commit is contained in:
committed by
Mark Fisher
parent
80b1d28be1
commit
a375e61260
@@ -30,12 +30,6 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.OffsetRequest;
|
||||
import kafka.serializer.Decoder;
|
||||
import kafka.serializer.DefaultDecoder;
|
||||
import kafka.utils.ZkUtils;
|
||||
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import org.I0Itec.zkclient.exception.ZkMarshallingError;
|
||||
import org.I0Itec.zkclient.serialize.ZkSerializer;
|
||||
@@ -43,8 +37,14 @@ import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.ByteArraySerializer;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderException;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
@@ -79,13 +79,12 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.OffsetRequest;
|
||||
import kafka.serializer.Decoder;
|
||||
import kafka.serializer.DefaultDecoder;
|
||||
import kafka.utils.ZkUtils;
|
||||
import scala.collection.Seq;
|
||||
|
||||
/**
|
||||
@@ -240,11 +239,11 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
|
||||
private final ZookeeperConnect zookeeperConnect;
|
||||
|
||||
private String brokers;
|
||||
private final String brokers;
|
||||
|
||||
private String[] headersToMap;
|
||||
|
||||
private String zkAddress;
|
||||
private final String zkAddress;
|
||||
|
||||
// -------- Default values for properties -------
|
||||
private int defaultReplicationFactor = 1;
|
||||
@@ -469,12 +468,14 @@ public class KafkaMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, Properties properties) {
|
||||
// Usage of a different consumer group each time achieves pub-sub
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, String group, 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 group = UUID.randomUUID().toString();
|
||||
createKafkaConsumer(name, inputChannel, properties, group, OffsetRequest.LatestTime());
|
||||
String consumerGroup = group == null ? UUID.randomUUID().toString() : group;
|
||||
createKafkaConsumer(name, inputChannel, properties, consumerGroup, OffsetRequest.LatestTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,13 +29,14 @@ 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.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.KafkaTestSupport;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -44,9 +45,9 @@ import org.springframework.integration.kafka.core.Partition;
|
||||
import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer;
|
||||
import org.springframework.integration.kafka.listener.MessageListener;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import kafka.api.OffsetRequest;
|
||||
|
||||
|
||||
/**
|
||||
@@ -70,7 +71,7 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binder getBinder() {
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
if (binder == null) {
|
||||
binder = createKafkaTestBinder();
|
||||
}
|
||||
@@ -128,7 +129,7 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
byte[] ratherBigPayload = new byte[2048];
|
||||
Arrays.fill(ratherBigPayload, (byte) 65);
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
|
||||
for (String codec : codecs) {
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
@@ -311,6 +312,14 @@ public class KafkaBinderTests extends PartitionCapableBinderTests {
|
||||
binder.unbindConsumers("foo" + uniqueBindingId + ".0");
|
||||
}
|
||||
|
||||
@Override @Ignore // TODO
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
}
|
||||
|
||||
@Override @Ignore // TODO
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Kafka binder does not support direct binding")
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,9 +18,6 @@ package org.springframework.cloud.stream.binder.kafka;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.KafkaTestSupport;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.TestKafkaCluster;
|
||||
@@ -31,6 +28,9 @@ import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.kafka.support.ZookeeperConnect;
|
||||
import org.springframework.xd.tuple.serializer.kryo.TupleKryoRegistrar;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
|
||||
/**
|
||||
* Test support class for {@link KafkaMessageChannelBinder}.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.xd.tuple.serializer.kryo.TupleKryoRegistrar;
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class KafkaTestBinder extends AbstractTestBinder<KafkaMessageChannelBinder> {
|
||||
|
||||
@@ -77,10 +78,10 @@ public class KafkaTestBinder extends AbstractTestBinder<KafkaMessageChannelBinde
|
||||
private static Codec getCodec() {
|
||||
return new PojoCodec(new TupleRegistrar());
|
||||
}
|
||||
|
||||
|
||||
//TODO: temporary wrapper for compatibility with SI Codec types
|
||||
private static class TupleRegistrar implements KryoRegistrar {
|
||||
private TupleKryoRegistrar delegate = new TupleKryoRegistrar();
|
||||
private final TupleKryoRegistrar delegate = new TupleKryoRegistrar();
|
||||
|
||||
@Override
|
||||
public void registerTypes(Kryo kryo) {
|
||||
|
||||
@@ -32,7 +32,11 @@ import java.util.Properties;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
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;
|
||||
@@ -40,16 +44,14 @@ 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;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.TestUtils;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
*/
|
||||
|
||||
public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@@ -62,7 +64,7 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport");
|
||||
properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport");
|
||||
@@ -116,7 +118,7 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "payload[0]");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
@@ -184,7 +186,7 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
@Test
|
||||
@Override
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
// Test pub/sub by emulating how StreamPlugin handles taps
|
||||
DirectChannel tapChannel = new DirectChannel();
|
||||
@@ -192,18 +194,18 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
QueueChannel module2InputChannel = new QueueChannel();
|
||||
QueueChannel module3InputChannel = new QueueChannel();
|
||||
// Create the tap first
|
||||
String fooTapName = binder.isCapable(Binder.Capability.DURABLE_PUBSUB) ? "foo.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null);
|
||||
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("tap:baz.http", tapChannel, null);
|
||||
binder.bindPubSubProducer(fooTapName, tapChannel, null);
|
||||
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = binder.isCapable(Binder.Capability.DURABLE_PUBSUB) ? "bar.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null);
|
||||
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;
|
||||
@@ -241,15 +243,15 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindProducers("tap:baz.http");
|
||||
binder.unbindProducers("baz.0");
|
||||
binder.unbindConsumers("baz.0");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testSendAndReceive() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
@@ -273,9 +275,10 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
// Test pub/sub by emulating how StreamPlugin handles taps
|
||||
DirectChannel tapChannel = new DirectChannel();
|
||||
@@ -285,13 +288,13 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
moduleOutputChannel.addInterceptor(new WireTap(tapChannel));
|
||||
binder.bindPubSubProducer("tap:baz.http", tapChannel, null);
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = binder.isCapable(Binder.Capability.DURABLE_PUBSUB) ? "foo.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null);
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubProducer(fooTapName, tapChannel, null);
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null, null);
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = binder.isCapable(Binder.Capability.DURABLE_PUBSUB) ? "bar.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null);
|
||||
String barTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null, null);
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
@@ -329,8 +332,8 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
binder.unbindConsumer(fooTapName, module2InputChannel);
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindProducers("tap:baz.http");
|
||||
binder.unbindProducers("baz.0");
|
||||
binder.unbindConsumers("baz.0");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.ExecutorChannel;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
@@ -40,10 +44,6 @@ import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
|
||||
/**
|
||||
* A simple implementation of {@link org.springframework.cloud.stream.binder.Binder} for in-process use. For inbound and outbound, creates a
|
||||
@@ -224,7 +224,8 @@ public class LocalMessageChannelBinder extends MessageChannelBinderSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
validateConsumerProperties(name, properties, CONSUMER_STANDARD_PROPERTIES);
|
||||
doRegisterConsumer(name, moduleInputChannel, this.pubsubChannelProvider, properties);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.local;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -23,9 +29,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
@@ -35,18 +43,12 @@ import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -56,7 +58,7 @@ import static org.junit.Assert.assertTrue;
|
||||
public class LocalBinderTests extends AbstractBinderTests {
|
||||
|
||||
@Override
|
||||
protected Binder getBinder() throws Exception {
|
||||
protected Binder<MessageChannel> getBinder() throws Exception {
|
||||
LocalMessageChannelBinder binder = new LocalMessageChannelBinder();
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
applicationContext.getBeanFactory().registerSingleton(
|
||||
@@ -72,7 +74,8 @@ public class LocalBinderTests extends AbstractBinderTests {
|
||||
return binder;
|
||||
}
|
||||
|
||||
protected Collection<?> getBindings(Binder testBinder) {
|
||||
@Override
|
||||
protected Collection<?> getBindings(Binder<MessageChannel> testBinder) {
|
||||
return getBindingsFromBinder(testBinder);
|
||||
}
|
||||
|
||||
@@ -130,7 +133,7 @@ public class LocalBinderTests extends AbstractBinderTests {
|
||||
tapped.countDown();
|
||||
throw new RuntimeException("bang");
|
||||
}
|
||||
}, null);
|
||||
}, null, null);
|
||||
moduleOutputChannel.send(new GenericMessage<String>("Foo"));
|
||||
assertTrue(tapped.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(messageReceived.get());
|
||||
@@ -163,6 +166,14 @@ public class LocalBinderTests extends AbstractBinderTests {
|
||||
assertTrue(msgSent.get());
|
||||
}
|
||||
|
||||
@Override @Ignore // TODO
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
}
|
||||
|
||||
@Override @Ignore // TODO
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
}
|
||||
|
||||
static class TestPayload {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,13 +25,13 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.stream.binder.BindingCleaner;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.binder.BindingCleaner;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.RabbitAdminException;
|
||||
import org.springframework.cloud.stream.binder.RabbitManagementUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(RabbitBindingCleaner.class);
|
||||
|
||||
public static final String BINDER_PREFIX = "binder.rabbit.";
|
||||
public static final String BINDER_PREFIX = "binder.";
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> clean(String entity, boolean isJob) {
|
||||
@@ -69,21 +69,7 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
List<String> removedQueues = isJob
|
||||
? null
|
||||
: findStreamQueues(adminUri, vhost, binderPrefix, entity, restTemplate);
|
||||
ExchangeCandidateCallback callback = null;
|
||||
if (isJob) {
|
||||
}
|
||||
else {
|
||||
final String tapPrefix = adjustPrefix(MessageChannelBinderSupport.applyPrefix(binderPrefix,
|
||||
MessageChannelBinderSupport.applyPubSub(BinderUtils.constructTapPrefix(entity))));
|
||||
callback = new ExchangeCandidateCallback() {
|
||||
|
||||
@Override
|
||||
public boolean isCandidate(String exchangeName) {
|
||||
return exchangeName.startsWith(tapPrefix);
|
||||
}
|
||||
};
|
||||
}
|
||||
List<String> removedExchanges = findExchanges(adminUri, vhost, binderPrefix, entity, restTemplate, callback);
|
||||
List<String> removedExchanges = findExchanges(adminUri, vhost, binderPrefix, entity, restTemplate);
|
||||
// Delete the queues in reverse order to enable re-running after a partial success.
|
||||
// The queue search above starts with 0 and terminates on a not found.
|
||||
for (int i = removedQueues.size() - 1; i >= 0; i--) {
|
||||
@@ -157,20 +143,21 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> findExchanges(String adminUri, String vhost, String binderPrefix, String entity,
|
||||
RestTemplate restTemplate, ExchangeCandidateCallback callback) {
|
||||
RestTemplate restTemplate) {
|
||||
List<String> removedExchanges = new ArrayList<>();
|
||||
URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}")
|
||||
.buildAndExpand(vhost).encode().toUri();
|
||||
List<Map<String, Object>> exchanges = restTemplate.getForObject(uri, List.class);
|
||||
String exchangeNamePrefix = adjustPrefix(MessageChannelBinderSupport.applyPrefix(binderPrefix, entity));
|
||||
for (Map<String, Object> exchange : exchanges) {
|
||||
String exchangeName = (String) exchange.get("name");
|
||||
if (callback.isCandidate(exchangeName)) {
|
||||
if (exchangeName.startsWith(exchangeNamePrefix)) {
|
||||
uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}", "{name}", "bindings", "source")
|
||||
.buildAndExpand(vhost, exchangeName).encode().toUri();
|
||||
List<Map<String, Object>> bindings = restTemplate.getForObject(uri, List.class);
|
||||
if (bindings.size() == 0) {
|
||||
if (hasNoForeignBindings(bindings, exchangeNamePrefix)) {
|
||||
uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}", "{name}", "bindings", "destination")
|
||||
.buildAndExpand(vhost, exchangeName).encode().toUri();
|
||||
@@ -192,9 +179,19 @@ public class RabbitBindingCleaner implements BindingCleaner {
|
||||
return removedExchanges;
|
||||
}
|
||||
|
||||
private interface ExchangeCandidateCallback {
|
||||
|
||||
boolean isCandidate(String exchangeName);
|
||||
private boolean hasNoForeignBindings(List<Map<String, Object>> bindings, String exchangeNamePrefix) {
|
||||
if (bindings.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
boolean noForeign = true;
|
||||
for (Map<String, Object> binding : bindings) {
|
||||
if (!("queue".equals(binding.get("destination_type")))
|
||||
|| !((String) binding.get("destination")).startsWith(exchangeNamePrefix)) {
|
||||
noForeign = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return noForeign;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,15 +29,16 @@ import java.util.Set;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.DirectExchange;
|
||||
import org.springframework.amqp.core.Exchange;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.amqp.core.MessagePostProcessor;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.BatchingRabbitTemplate;
|
||||
@@ -445,6 +446,8 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
}
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String queueName = applyPrefix(accessor.getPrefix(this.defaultPrefix), name);
|
||||
TopicExchange exchange = new TopicExchange(queueName);
|
||||
declareExchangeIfNotPresent(exchange);
|
||||
int partitionIndex = accessor.getPartitionIndex();
|
||||
if (partitionIndex >= 0) {
|
||||
queueName += "-" + partitionIndex;
|
||||
@@ -453,20 +456,23 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
Queue queue = new Queue(queueName, true, false, false, args);
|
||||
declareQueueIfNotPresent(queue);
|
||||
autoBindDLQ(name, accessor);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(queueName);
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
doRegisterConsumer(name, moduleInputChannel, queue, accessor, false);
|
||||
bindExistingProducerDirectlyIfPossible(name, moduleInputChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
String exchangeName = BinderUtils.removeGroupFromPubSub(name);
|
||||
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);
|
||||
FanoutExchange exchange = new FanoutExchange(applyPrefix(prefix, applyPubSub(exchangeName)));
|
||||
TopicExchange exchange = new TopicExchange(applyPrefix(prefix, exchangeName));
|
||||
declareExchangeIfNotPresent(exchange);
|
||||
Queue queue;
|
||||
boolean durable = accessor.isDurable(this.defaultDurableSubscription);
|
||||
@@ -479,14 +485,14 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
queue = new Queue(queueName, false, false, true);
|
||||
}
|
||||
declareQueueIfNotPresent(queue);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with("#");
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
// register with context so they will be redeclared after a connection failure
|
||||
if (!this.autoDeclareContext.containsBean(applyPubSub(name))) {
|
||||
this.autoDeclareContext.getBeanFactory().registerSingleton(applyPubSub(name), queue);
|
||||
// register with context so they will be redeclared after a connection failure if auto-delete
|
||||
if (!durable && !this.autoDeclareContext.containsBean(name)) {
|
||||
this.autoDeclareContext.getBeanFactory().registerSingleton(name, queue);
|
||||
}
|
||||
String bindingBeanName = exchange.getName() + "." + queue.getName() + ".binding";
|
||||
if (!this.autoDeclareContext.containsBean(bindingBeanName)) {
|
||||
if (!durable && !this.autoDeclareContext.containsBean(bindingBeanName)) {
|
||||
this.autoDeclareContext.getBeanFactory().registerSingleton(bindingBeanName, binding);
|
||||
}
|
||||
doRegisterConsumer(name, moduleInputChannel, queue, accessor, true);
|
||||
@@ -610,24 +616,35 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
|
||||
private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitPropertiesAccessor properties,
|
||||
RabbitTemplate rabbitTemplate) {
|
||||
String queueName = applyPrefix(properties.getPrefix(this.defaultPrefix), name);
|
||||
String prefix = properties.getPrefix(this.defaultPrefix);
|
||||
String queueName = applyPrefix(prefix, name);
|
||||
String partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass();
|
||||
Expression partitionKeyExpression = properties.getPartitionKeyExpression();
|
||||
AmqpOutboundEndpoint queue = new AmqpOutboundEndpoint(rabbitTemplate);
|
||||
TopicExchange exchange = new TopicExchange(queueName);
|
||||
declareExchangeIfNotPresent(exchange);
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
|
||||
endpoint.setExchangeName(exchange.getName());
|
||||
if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) {
|
||||
declareQueueIfNotPresent(new Queue(queueName));
|
||||
queue.setRoutingKey(queueName); // uses default exchange
|
||||
Queue queue = new Queue(queueName);
|
||||
declareQueueIfNotPresent(queue);
|
||||
endpoint.setRoutingKey(queueName);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(queueName);
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
else {
|
||||
queue.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression
|
||||
endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression
|
||||
(queueName)));
|
||||
// if the stream is partitioned, create one queue for each target partition
|
||||
for (int i = 0; i < properties.getNextModuleCount(); i++) {
|
||||
this.rabbitAdmin.declareQueue(new Queue(queueName + "-" + i));
|
||||
Queue queue = new Queue(queueName + "-" + i);
|
||||
this.rabbitAdmin.declareQueue(queue);
|
||||
org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange)
|
||||
.with(queue.getName());
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
}
|
||||
configureOutboundHandler(queue, properties);
|
||||
return queue;
|
||||
configureOutboundHandler(endpoint, properties);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private void configureOutboundHandler(AmqpOutboundEndpoint handler, RabbitPropertiesAccessor properties) {
|
||||
@@ -645,12 +662,13 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
Properties properties) {
|
||||
validateProducerProperties(name, properties, SUPPORTED_PUBSUB_PRODUCER_PROPERTIES);
|
||||
RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties);
|
||||
String exchangeName = applyPrefix(accessor.getPrefix(this.defaultPrefix), applyPubSub(name));
|
||||
declareExchangeIfNotPresent(new FanoutExchange(exchangeName));
|
||||
AmqpOutboundEndpoint fanout = new AmqpOutboundEndpoint(determineRabbitTemplate(accessor));
|
||||
fanout.setExchangeName(exchangeName);
|
||||
configureOutboundHandler(fanout, accessor);
|
||||
doRegisterProducer(name, moduleOutputChannel, fanout, accessor);
|
||||
String exchangeName = applyPrefix(accessor.getPrefix(this.defaultPrefix), name);
|
||||
declareExchangeIfNotPresent(new TopicExchange(exchangeName));
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(determineRabbitTemplate(accessor));
|
||||
endpoint.setExchangeName(exchangeName);
|
||||
endpoint.setRoutingKey(name);
|
||||
configureOutboundHandler(endpoint, accessor);
|
||||
doRegisterProducer(name, moduleOutputChannel, endpoint, accessor);
|
||||
}
|
||||
|
||||
private RabbitTemplate determineRabbitTemplate(RabbitPropertiesAccessor properties) {
|
||||
@@ -811,22 +829,18 @@ public class RabbitMessageChannelBinder extends MessageChannelBinderSupport impl
|
||||
cleanAutoDeclareContext(name);
|
||||
}
|
||||
|
||||
private void cleanAutoDeclareContext(String name) {
|
||||
if (this.autoDeclareContext.containsBean(applyPubSub(name))) {
|
||||
ConfigurableListableBeanFactory beanFactory = this.autoDeclareContext.getBeanFactory();
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
((DefaultListableBeanFactory) beanFactory).destroySingleton(applyPubSub(name));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
super.unbindPubSubConsumers(name, group);
|
||||
cleanAutoDeclareContext(BinderUtils.groupedName(name, group));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapable(Capability capability) {
|
||||
switch (capability) {
|
||||
case DURABLE_PUBSUB:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
private void cleanAutoDeclareContext(String name) {
|
||||
if (this.autoDeclareContext.containsBean(name)) {
|
||||
ConfigurableListableBeanFactory beanFactory = this.autoDeclareContext.getBeanFactory();
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
((DefaultListableBeanFactory) beanFactory).destroySingleton(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,37 +17,37 @@
|
||||
package org.springframework.cloud.stream.binder.rabbit;
|
||||
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.DefaultConsumer;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.ChannelCallback;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.RabbitAdminException;
|
||||
import org.springframework.cloud.stream.binder.RabbitManagementUtils;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.DefaultConsumer;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -55,7 +55,7 @@ import static org.junit.Assert.fail;
|
||||
*/
|
||||
public class RabbitBinderCleanerTests {
|
||||
|
||||
private static final String BINDER_PREFIX = "binder.rabbit.";
|
||||
private static final String BINDER_PREFIX = "binder.";
|
||||
|
||||
@Rule
|
||||
public RabbitTestSupport rabbitWithMgmtEnabled = new RabbitTestSupport(true);
|
||||
@@ -63,16 +63,16 @@ public class RabbitBinderCleanerTests {
|
||||
@Test
|
||||
public void testCleanStream() {
|
||||
final RabbitBindingCleaner cleaner = new RabbitBindingCleaner();
|
||||
final RestTemplate template = RabbitManagementUtils.buildRestTemplate("http://localhost:15672", "guest",
|
||||
final RestTemplate template = RabbitManagementUtils.buildRestTemplate("http://localhost:15672", "guest",
|
||||
"guest");
|
||||
final String stream1 = UUID.randomUUID().toString();
|
||||
String stream2 = stream1 + "-1";
|
||||
String firstQueue = null;
|
||||
CachingConnectionFactory connectionFactory = rabbitWithMgmtEnabled.getResource();
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
String queue1Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX,
|
||||
BinderUtils.constructPipeName(stream1, i));
|
||||
String queue2Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX,
|
||||
BinderUtils.constructPipeName(stream2, i));
|
||||
String queue1Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + "." + i);
|
||||
String queue2Name = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream2 + "." + i);
|
||||
if (firstQueue == null) {
|
||||
firstQueue = queue1Name;
|
||||
}
|
||||
@@ -90,25 +90,29 @@ public class RabbitBinderCleanerTests {
|
||||
.pathSegment("{vhost}", "{queue}")
|
||||
.buildAndExpand("/", MessageChannelBinderSupport.constructDLQName(queue1Name)).encode().toUri();
|
||||
template.put(uri, new AmqpQueue(false, true));
|
||||
TopicExchange exchange = new TopicExchange(queue1Name);
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue1Name)).to(exchange).with(queue1Name));
|
||||
exchange = new TopicExchange(queue2Name);
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue2Name)).to(exchange).with(queue2Name));
|
||||
}
|
||||
CachingConnectionFactory connectionFactory = rabbitWithMgmtEnabled.getResource();
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
final FanoutExchange fanout1 = new FanoutExchange(
|
||||
MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, MessageChannelBinderSupport.applyPubSub(
|
||||
BinderUtils.constructTapPrefix(stream1) + ".foo.bar")));
|
||||
rabbitAdmin.declareExchange(fanout1);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(fanout1));
|
||||
final FanoutExchange fanout2 = new FanoutExchange(
|
||||
MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, MessageChannelBinderSupport.applyPubSub(
|
||||
BinderUtils.constructTapPrefix(stream2) + ".foo.bar")));
|
||||
rabbitAdmin.declareExchange(fanout2);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(fanout2));
|
||||
final TopicExchange topic1 = new TopicExchange(
|
||||
MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + ".foo.bar"));
|
||||
rabbitAdmin.declareExchange(topic1);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic1).with("#"));
|
||||
String foreignQueue = UUID.randomUUID().toString();
|
||||
rabbitAdmin.declareQueue(new Queue(foreignQueue));
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(foreignQueue)).to(topic1).with("#"));
|
||||
final TopicExchange topic2 = new TopicExchange(
|
||||
MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream2 + ".foo.bar"));
|
||||
rabbitAdmin.declareExchange(topic2);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic2).with("#"));
|
||||
new RabbitTemplate(connectionFactory).execute(new ChannelCallback<Void>() {
|
||||
|
||||
@Override
|
||||
public Void doInRabbit(Channel channel) throws Exception {
|
||||
String queueName = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX,
|
||||
BinderUtils.constructPipeName(stream1, 4));
|
||||
String queueName = MessageChannelBinderSupport.applyPrefix(BINDER_PREFIX, stream1 + "." + 4);
|
||||
String consumerTag = channel.basicConsume(queueName, new DefaultConsumer(channel));
|
||||
try {
|
||||
waitForConsumerStateNot(queueName, 0);
|
||||
@@ -125,8 +129,8 @@ public class RabbitBinderCleanerTests {
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (RabbitAdminException e) {
|
||||
assertThat(e.getMessage(), startsWith("Cannot delete exchange " +
|
||||
fanout1.getName() + "; it has bindings:"));
|
||||
assertThat(e.getMessage(), containsString("Cannot delete exchange "));
|
||||
assertThat(e.getMessage(), containsString("; it has bindings:"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -148,8 +152,9 @@ public class RabbitBinderCleanerTests {
|
||||
}
|
||||
|
||||
});
|
||||
rabbitAdmin.deleteExchange(fanout1.getName()); // easier than deleting the binding
|
||||
rabbitAdmin.declareExchange(fanout1);
|
||||
rabbitAdmin.deleteExchange(topic1.getName()); // easier than deleting the binding
|
||||
rabbitAdmin.declareExchange(topic1);
|
||||
rabbitAdmin.deleteQueue(foreignQueue);
|
||||
connectionFactory.destroy();
|
||||
Map<String, List<String>> cleanedMap = cleaner.clean(stream1, false);
|
||||
assertEquals(2, cleanedMap.size());
|
||||
@@ -161,8 +166,7 @@ public class RabbitBinderCleanerTests {
|
||||
assertEquals(BINDER_PREFIX + stream1 + "." + i + ".dlq", cleanedQueues.get(i * 2 + 1));
|
||||
}
|
||||
List<String> cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertEquals(1, cleanedExchanges.size());
|
||||
assertEquals(fanout1.getName(), cleanedExchanges.get(0));
|
||||
assertEquals(6, cleanedExchanges.size());
|
||||
|
||||
// wild card *should* clean stream2
|
||||
cleanedMap = cleaner.clean(stream1 + "*", false);
|
||||
@@ -173,8 +177,7 @@ public class RabbitBinderCleanerTests {
|
||||
assertEquals(BINDER_PREFIX + stream2 + "." + i, cleanedQueues.get(i));
|
||||
}
|
||||
cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertEquals(1, cleanedExchanges.size());
|
||||
assertEquals(fanout2.getName(), cleanedExchanges.get(0));
|
||||
assertEquals(6, cleanedExchanges.size());
|
||||
}
|
||||
|
||||
public static class AmqpQueue {
|
||||
|
||||
@@ -16,6 +16,21 @@
|
||||
|
||||
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;
|
||||
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.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -53,26 +68,12 @@ import org.springframework.integration.channel.QueueChannel;
|
||||
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.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
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.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
@@ -88,7 +89,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport();
|
||||
|
||||
@Override
|
||||
protected Binder getBinder() {
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
if (testBinder == null) {
|
||||
testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource());
|
||||
}
|
||||
@@ -102,12 +103,12 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveBad() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
binder.bindProducer("bad.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("bad.0", moduleInputChannel, null);
|
||||
Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
@@ -126,7 +127,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
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);
|
||||
@@ -177,7 +178,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
assertEquals("foo.props.0", container.getQueueNames()[0]);
|
||||
|
||||
try {
|
||||
binder.bindPubSubConsumer("dummy", null, properties);
|
||||
binder.bindPubSubConsumer("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
@@ -202,13 +203,12 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
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();
|
||||
assertEquals(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "props.0", TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKey"));
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
assertEquals(MessageDeliveryMode.PERSISTENT, mode);
|
||||
@@ -233,7 +233,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
endpoint = bindings.get(0).getEndpoint();
|
||||
assertEquals(
|
||||
"'foo.props.0-' + headers['partition']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
SpelExpression.class).getExpressionString());
|
||||
mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
@@ -273,7 +273,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRequestReplyRequestorProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
@@ -299,8 +299,6 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
assertEquals(2, bindings.size());
|
||||
AbstractEndpoint endpoint = bindings.get(0).getEndpoint(); // producer
|
||||
assertEquals("foo.props.0.requests",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKey"));
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
assertEquals(MessageDeliveryMode.NON_PERSISTENT, mode);
|
||||
@@ -339,7 +337,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRequestReplyReplierProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", "foo.");
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
@@ -367,7 +365,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
AbstractEndpoint endpoint = bindings.get(1).getEndpoint(); // producer
|
||||
assertEquals(
|
||||
"headers['amqp_replyTo']",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
SpelExpression.class).getExpressionString());
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
@@ -410,7 +408,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public void testDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
@@ -428,14 +426,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindPubSubConsumer("teststream.tap:stream:durabletest.0", moduleInputChannel, properties);
|
||||
binder.bindPubSubConsumer("durabletest.0", moduleInputChannel, "tgroup", properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend(TEST_PREFIX + "topic.tap:stream:durabletest.0", "", "foo");
|
||||
template.convertAndSend(TEST_PREFIX + "durabletest.0", "", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "teststream.tap:stream:durabletest.0.dlq");
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "tgroup.durabletest.0.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertEquals("foo", deadLetter);
|
||||
break;
|
||||
@@ -444,11 +442,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
assertTrue(n < 100);
|
||||
|
||||
binder.unbindConsumer("teststream.tap:stream:durabletest.0", moduleInputChannel);
|
||||
assertNotNull(admin.getQueueProperties(TEST_PREFIX + "teststream.tap:stream:durabletest.0.dlq"));
|
||||
admin.deleteQueue(TEST_PREFIX + "teststream.tap:stream:durabletest.0.dlq");
|
||||
admin.deleteQueue(TEST_PREFIX + "teststream.tap:stream:durabletest.0");
|
||||
admin.deleteExchange(TEST_PREFIX + "topic.tap:stream:durabletest.0");
|
||||
binder.unbindConsumer("durabletest.0", moduleInputChannel);
|
||||
binder.unbindPubSubConsumers("durabletest.0", "tgroup1");
|
||||
binder.unbindPubSubConsumers("durabletest.0", "tgroup2");
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
public void testNonDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
@@ -473,19 +473,19 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
});
|
||||
binder.bindPubSubConsumer("teststream.tap:stream:nondurabletest.0", moduleInputChannel, properties);
|
||||
binder.bindPubSubConsumer("nondurabletest.0", moduleInputChannel, "tgroup", properties);
|
||||
|
||||
binder.unbindConsumer("teststream.tap:stream:nondurabletest.0", moduleInputChannel);
|
||||
assertNull(admin.getQueueProperties(TEST_PREFIX + "teststream.tap:stream:nondurabletest.0.dlq"));
|
||||
admin.deleteQueue(TEST_PREFIX + "teststream.tap:stream:nondurabletest.0");
|
||||
admin.deleteExchange(TEST_PREFIX + "topic.tap:stream:nondurabletest.0");
|
||||
binder.unbindPubSubConsumers("nondurabletest.0", "tgroup");
|
||||
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 binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
@@ -532,7 +532,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
Queue queue = new Queue(TEST_PREFIX + "dlqpubtest", true, false, false, args);
|
||||
admin.declareQueue(queue);
|
||||
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("prefix", TEST_PREFIX);
|
||||
properties.put("autoBindDLQ", "true");
|
||||
@@ -576,7 +576,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
@Test
|
||||
public void testBatchingAndCompression() throws Exception {
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("deliveryMode", "NON_PERSISTENT");
|
||||
properties.put("batchingEnabled", "true");
|
||||
@@ -689,13 +689,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Override
|
||||
protected String getEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression",
|
||||
SpelExpression.class).getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.exchangeNameExpression",
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.exchangeNameExpression",
|
||||
SpelExpression.class).getExpressionString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder.rabbit;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,10 +39,12 @@ import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
*/
|
||||
public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBinder> {
|
||||
|
||||
public static final String BINDER_PREFIX = "binder.rabbit.";
|
||||
public static final String BINDER_PREFIX = "binder.";
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private final Set<String> prefixes = new HashSet<>();
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory) {
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(connectionFactory);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
@@ -53,6 +59,51 @@ public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBin
|
||||
this.rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindConsumer(String name, MessageChannel moduleInputChannel, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
super.bindConsumer(name, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) {
|
||||
capturePrefix(properties);
|
||||
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) {
|
||||
if (properties != null) {
|
||||
String prefix = properties.getProperty("prefix");
|
||||
if (prefix != null) {
|
||||
this.prefixes.add(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (!queues.isEmpty()) {
|
||||
@@ -62,11 +113,16 @@ public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBin
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rabbitAdmin.deleteQueue(BINDER_PREFIX + queue + "-" + i);
|
||||
}
|
||||
rabbitAdmin.deleteQueue("foo." + queue);
|
||||
// delete any partitioned queues
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rabbitAdmin.deleteQueue("foo." + 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);
|
||||
}
|
||||
}
|
||||
if (!topics.isEmpty()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
log4j.rootCategory=WARN, stdout
|
||||
log4j.rootCategory=DEBUG, stdout
|
||||
|
||||
# standard logging including calling site
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
|
||||
@@ -23,7 +23,13 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderHeaders;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
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.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@@ -47,12 +53,6 @@ import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderPropertiesAccessor;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.MessageChannelBinderSupport;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by Redis.
|
||||
@@ -210,7 +210,7 @@ public class RedisMessageChannelBinder extends MessageChannelBinderSupport imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(final String name, MessageChannel moduleInputChannel,
|
||||
public void bindPubSubConsumer(final String name, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("declaring pubsub for inbound: " + name);
|
||||
|
||||
@@ -34,10 +34,16 @@ 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;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -47,20 +53,16 @@ import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BinderProperties;
|
||||
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = RedisMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
@@ -72,7 +74,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
new EmbeddedHeadersMessageConverter();
|
||||
|
||||
@Override
|
||||
protected Binder getBinder() {
|
||||
protected Binder<MessageChannel> getBinder() {
|
||||
if (testBinder == null) {
|
||||
testBinder = new RedisTestBinder(redisAvailableRule.getResource());
|
||||
}
|
||||
@@ -85,6 +87,8 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
@Ignore // TODO
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
|
||||
TimeUnit.SECONDS.sleep(2); //TODO remove timing issue
|
||||
@@ -99,7 +103,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("maxAttempts", "1"); // disable retry
|
||||
binder.bindConsumer("props.0", new DirectChannel(), properties);
|
||||
@@ -125,7 +129,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
verifyConsumer(endpoint);
|
||||
|
||||
try {
|
||||
binder.bindPubSubConsumer("dummy", null, properties);
|
||||
binder.bindPubSubConsumer("dummy", null, null, properties);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
@@ -150,7 +154,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
binder.bindProducer("props.0", new DirectChannel(), null);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Binding> bindings = TestUtils.getPropertyValue(binder, "binder.bindings", List.class);
|
||||
@@ -209,7 +213,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRequestReplyRequestorProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
@@ -257,7 +261,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRequestReplyReplierProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.put("backOffInitialInterval", "2000");
|
||||
@@ -324,7 +328,7 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
|
||||
@Test
|
||||
public void testRetryFail() {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel channel = new DirectChannel();
|
||||
binder.bindProducer("retry.0", channel, null);
|
||||
Properties props = new Properties();
|
||||
@@ -348,6 +352,10 @@ public class RedisBinderTests extends PartitionCapableBinderTests {
|
||||
assertTrue(headers.contains("bar"));
|
||||
}
|
||||
|
||||
@Override @Ignore // TODO
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
}
|
||||
|
||||
private RedisTemplate<String, Object> createTemplate() {
|
||||
if (this.redisTemplate != null) {
|
||||
return this.redisTemplate;
|
||||
|
||||
@@ -39,14 +39,15 @@ public interface Binder<T> {
|
||||
*/
|
||||
void bindConsumer(String name, T inboundBindTarget, Properties properties);
|
||||
|
||||
|
||||
/**
|
||||
* Bind a message consumer on a pub/sub channel
|
||||
* @param name the logical identity of the message source
|
||||
* @param inboundBindTarget the module interface to be bound as a pub/sub consumer
|
||||
* @param group the consumer group to which this consumer belongs - subscriptions are shared among consumers
|
||||
* in the same group
|
||||
* @param properties arbitrary String key/value pairs that will be used in the binding
|
||||
*/
|
||||
void bindPubSubConsumer(final String name, T inboundBindTarget, Properties properties);
|
||||
void bindPubSubConsumer(final String name, T inboundBindTarget, String group, Properties properties);
|
||||
|
||||
/**
|
||||
* Bind a message producer on a p2p channel.
|
||||
@@ -71,6 +72,14 @@ public interface Binder<T> {
|
||||
*/
|
||||
void unbindConsumers(String name);
|
||||
|
||||
/**
|
||||
* Unbind inbound module components and stop any active components that use the channel
|
||||
* with the supplied consumer group.
|
||||
* @param name the channel name
|
||||
* @param group the consumer group
|
||||
*/
|
||||
void unbindPubSubConsumers(String name, String group);
|
||||
|
||||
/**
|
||||
* Unbind outbound module components and stop any active components that use the channel.
|
||||
* @param name the channel name
|
||||
@@ -128,21 +137,4 @@ public interface Binder<T> {
|
||||
*/
|
||||
T bindDynamicPubSubProducer(String name, Properties properties);
|
||||
|
||||
/**
|
||||
* Return true if the binder supports the capability.
|
||||
* @param capability the capability.
|
||||
* @return true if the capability is supported.
|
||||
*/
|
||||
boolean isCapable(Capability capability);
|
||||
|
||||
public enum Capability {
|
||||
|
||||
/**
|
||||
* When a binder supports durable subscriptions to a pub/sub channel, the stream
|
||||
* name will be included in the consumer name.
|
||||
*/
|
||||
DURABLE_PUBSUB
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -33,35 +31,11 @@ public class BinderUtils {
|
||||
*/
|
||||
public static final String GROUP_INDEX_DELIMITER = ".";
|
||||
|
||||
/**
|
||||
* The prefix for the consumer/producer when creating a tap.
|
||||
*/
|
||||
public static final String TAP_CHANNEL_PREFIX = "tap:";
|
||||
|
||||
/**
|
||||
* The prefix for the consumer/producer when creating a topic.
|
||||
*/
|
||||
public static final String TOPIC_CHANNEL_PREFIX = "topic:";
|
||||
|
||||
public static final Pattern PUBSUB_NAMED_CHANNEL_PATTERN = Pattern.compile("[^.]+\\.(tap|topic):");
|
||||
|
||||
public static String addGroupToPubSub(String group, String inputChannelName) {
|
||||
if (inputChannelName.startsWith(TAP_CHANNEL_PREFIX)
|
||||
|| inputChannelName.startsWith(TOPIC_CHANNEL_PREFIX)) {
|
||||
inputChannelName = group + "." + inputChannelName;
|
||||
}
|
||||
return inputChannelName;
|
||||
}
|
||||
|
||||
public static String removeGroupFromPubSub(String name) {
|
||||
if (PUBSUB_NAMED_CHANNEL_PATTERN.matcher(name).find()) {
|
||||
return name.substring(name.indexOf(".") + 1);
|
||||
}
|
||||
else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the provided channel name represents a pub/sub channel (i.e. topic or tap).
|
||||
* @param channelName name of the channel to check
|
||||
@@ -69,22 +43,17 @@ public class BinderUtils {
|
||||
*/
|
||||
public static boolean isChannelPubSub(String channelName) {
|
||||
Assert.isTrue(StringUtils.hasText(channelName), "Channel name should not be empty/null.");
|
||||
// Check if the channelName starts with tap: or topic:
|
||||
return (channelName.startsWith(TAP_CHANNEL_PREFIX) || channelName.startsWith(TOPIC_CHANNEL_PREFIX));
|
||||
return channelName.startsWith(TOPIC_CHANNEL_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a pipe name from the group and index.
|
||||
* Construct a name comprised of the group and name.
|
||||
* @param name the name.
|
||||
* @param group the group.
|
||||
* @param index the index.
|
||||
* @return the name.
|
||||
* @return the constructed name.
|
||||
*/
|
||||
public static String constructPipeName(String group, int index) {
|
||||
return group + GROUP_INDEX_DELIMITER + index;
|
||||
}
|
||||
|
||||
public static String constructTapPrefix(String group) {
|
||||
return TAP_CHANNEL_PREFIX + "stream:" + group;
|
||||
public static String groupedName(String name, String group) {
|
||||
return group == null ? name : group + BinderUtils.GROUP_INDEX_DELIMITER + name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -471,6 +471,11 @@ public abstract class MessageChannelBinderSupport
|
||||
deleteBindings("inbound." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
unbindConsumers(BinderUtils.groupedName(name, group));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
deleteBindings("outbound." + name);
|
||||
@@ -486,11 +491,6 @@ public abstract class MessageChannelBinderSupport
|
||||
deleteBinding("outbound." + name, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapable(Capability capability) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void addBinding(Binding binding) {
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
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;
|
||||
@@ -29,20 +36,10 @@ import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.channel.interceptor.WireTap;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.cloud.stream.binder.Binder.Capability;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -57,7 +54,7 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testClean() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
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);
|
||||
@@ -77,7 +74,7 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceive() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("foo.0", moduleOutputChannel, null);
|
||||
@@ -98,7 +95,7 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveNoOriginalContentType() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
binder.bindProducer("bar.0", moduleOutputChannel, null);
|
||||
@@ -118,23 +115,19 @@ public abstract class AbstractBinderTests {
|
||||
|
||||
@Test
|
||||
public void testSendAndReceivePubSub() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
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();
|
||||
binder.bindProducer("baz.0", moduleOutputChannel, null);
|
||||
binder.bindConsumer("baz.0", moduleInputChannel, null);
|
||||
moduleOutputChannel.addInterceptor(new WireTap(tapChannel));
|
||||
binder.bindPubSubProducer("tap:baz.http", tapChannel, null);
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = binder.isCapable(Capability.DURABLE_PUBSUB) ? "foo.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null);
|
||||
String fooTapName = "baz.0";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, "tgroup1", null);
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = binder.isCapable(Capability.DURABLE_PUBSUB) ? "bar.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null);
|
||||
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;
|
||||
@@ -163,7 +156,7 @@ public abstract class AbstractBinderTests {
|
||||
assertEquals("foo/bar", tapped2.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindConsumer(barTapName, module3InputChannel);
|
||||
binder.unbindPubSubConsumers(barTapName, "tgroup2");
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
moduleOutputChannel.send(message2);
|
||||
@@ -180,32 +173,28 @@ public abstract class AbstractBinderTests {
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindProducers("tap:baz.http");
|
||||
binder.unbindPubSubConsumers(fooTapName, "tgroup1");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createInboundPubSubBeforeOutboundPubSub() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
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 = binder.isCapable(Capability.DURABLE_PUBSUB) ? "foo.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(fooTapName, module2InputChannel, null);
|
||||
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);
|
||||
moduleOutputChannel.addInterceptor(new WireTap(tapChannel));
|
||||
binder.bindPubSubProducer("tap:baz.http", tapChannel, null);
|
||||
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = binder.isCapable(Capability.DURABLE_PUBSUB) ? "bar.tap:baz.http" : "tap:baz.http";
|
||||
binder.bindPubSubConsumer(barTapName, module3InputChannel, null);
|
||||
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;
|
||||
@@ -234,7 +223,7 @@ public abstract class AbstractBinderTests {
|
||||
assertEquals("foo/bar", tapped2.getHeaders().get(MessageHeaders.CONTENT_TYPE));
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
binder.unbindConsumer(barTapName, module3InputChannel);
|
||||
binder.unbindPubSubConsumers(barTapName, "tgroup2");
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar").setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
"foo/bar").build();
|
||||
moduleOutputChannel.send(message2);
|
||||
@@ -251,7 +240,7 @@ public abstract class AbstractBinderTests {
|
||||
// Clean up as StreamPlugin would
|
||||
binder.unbindConsumer("baz.0", moduleInputChannel);
|
||||
binder.unbindProducer("baz.0", moduleOutputChannel);
|
||||
binder.unbindProducers("tap:baz.http");
|
||||
binder.unbindPubSubConsumers(fooTapName, "tgroup1");
|
||||
assertTrue(getBindings(binder).isEmpty());
|
||||
}
|
||||
|
||||
@@ -259,7 +248,7 @@ public abstract class AbstractBinderTests {
|
||||
public void testBadDynamic() throws Exception {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(BinderProperties.PARTITION_KEY_EXPRESSION, "'foo'");
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
try {
|
||||
binder.bindDynamicProducer("queue:foo", properties);
|
||||
fail("Exception expected");
|
||||
@@ -275,19 +264,19 @@ public abstract class AbstractBinderTests {
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<?> getBindings(Binder testBinder) {
|
||||
protected Collection<?> getBindings(Binder<MessageChannel> testBinder) {
|
||||
if (testBinder instanceof AbstractTestBinder) {
|
||||
return getBindingsFromBinder(((AbstractTestBinder) testBinder).getCoreBinder());
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
protected Collection<?> getBindingsFromBinder(Binder binder) {
|
||||
protected Collection<?> getBindingsFromBinder(Binder<MessageChannel> binder) {
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(binder);
|
||||
return (List<?>) accessor.getPropertyValue("bindings");
|
||||
}
|
||||
|
||||
protected abstract Binder getBinder() throws Exception;
|
||||
protected abstract Binder<MessageChannel> getBinder() throws Exception;
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
|
||||
@@ -54,8 +54,8 @@ public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, Properties properties) {
|
||||
binder.bindPubSubConsumer(name, inputChannel, properties);
|
||||
public void bindPubSubConsumer(String name, MessageChannel inputChannel, String group, Properties properties) {
|
||||
binder.bindPubSubConsumer(name, inputChannel, group, properties);
|
||||
addTopic(name);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport>
|
||||
binder.unbindConsumers(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
binder.unbindPubSubConsumers(name, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
binder.unbindProducers(name);
|
||||
@@ -127,12 +132,7 @@ public abstract class AbstractTestBinder<C extends MessageChannelBinderSupport>
|
||||
return this.binder.bindDynamicPubSubProducer(name, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapable(Capability capability) {
|
||||
return this.binder.isCapable(capability);
|
||||
}
|
||||
|
||||
public Binder getBinder() {
|
||||
public C getBinder() {
|
||||
return this.binder;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.integration.channel.QueueChannel;
|
||||
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.support.GenericMessage;
|
||||
|
||||
|
||||
@@ -52,7 +53,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testBadProperties() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("foo", "bar");
|
||||
properties.put("baz", "qux");
|
||||
@@ -81,7 +82,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
Binder<MessageChannel> binder = getBinder();
|
||||
Properties properties = new Properties();
|
||||
properties.put("partitionKeyExpression", "payload");
|
||||
properties.put("partitionSelectorExpression", "hashCode()");
|
||||
@@ -182,7 +183,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
|
||||
@Test
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
Binder binder = getBinder();
|
||||
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");
|
||||
@@ -266,7 +267,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests {
|
||||
protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
protected abstract String getClassUnderTestName();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -42,9 +44,8 @@ import org.springframework.xd.tuple.Tuple;
|
||||
import org.springframework.xd.tuple.TupleBuilder;
|
||||
import org.springframework.xd.tuple.serializer.kryo.TupleKryoRegistrar;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -52,7 +53,7 @@ import static org.junit.Assert.assertSame;
|
||||
*/
|
||||
public class MessageChannelBinderSupportTests {
|
||||
|
||||
private ContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver();
|
||||
private final ContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver();
|
||||
|
||||
private final TestMessageChannelBinder binder = new TestMessageChannelBinder();
|
||||
|
||||
@@ -272,7 +273,7 @@ public class MessageChannelBinderSupportTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel,
|
||||
public void bindPubSubConsumer(String name, MessageChannel moduleInputChannel, String group,
|
||||
Properties properties) {
|
||||
}
|
||||
|
||||
@@ -298,7 +299,7 @@ public class MessageChannelBinderSupportTests {
|
||||
|
||||
//TODO: temporary wrapper for compatibility with SI Codec types
|
||||
private static class TupleRegistrar implements KryoRegistrar {
|
||||
private TupleKryoRegistrar delegate = new TupleKryoRegistrar();
|
||||
private final TupleKryoRegistrar delegate = new TupleKryoRegistrar();
|
||||
|
||||
@Override
|
||||
public void registerTypes(Kryo kryo) {
|
||||
|
||||
@@ -19,6 +19,14 @@ package org.springframework.cloud.stream.test.junit.kafka;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||
import org.junit.Rule;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
|
||||
|
||||
import kafka.server.KafkaConfig;
|
||||
import kafka.server.KafkaServer;
|
||||
import kafka.utils.SystemTime$;
|
||||
@@ -28,14 +36,6 @@ import kafka.utils.Utils;
|
||||
import kafka.utils.ZKStringSerializer$;
|
||||
import kafka.utils.ZkUtils;
|
||||
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import org.I0Itec.zkclient.exception.ZkInterruptedException;
|
||||
import org.junit.Rule;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
|
||||
|
||||
|
||||
/**
|
||||
* JUnit {@link Rule} that starts an embedded Kafka server (with an associated Zookeeper)
|
||||
@@ -62,7 +62,7 @@ public class KafkaTestSupport extends AbstractExternalResourceTestSupport<String
|
||||
|
||||
private KafkaServer kafkaServer;
|
||||
|
||||
private Properties brokerConfig = TestUtils.createBrokerConfig(0, TestUtils.choosePort(), false);
|
||||
private final Properties brokerConfig = TestUtils.createBrokerConfig(0, TestUtils.choosePort(), false);
|
||||
|
||||
// caches previous failures to reach the external server - preventing repeated retries
|
||||
private static boolean hasFailedAlready = false;
|
||||
@@ -108,8 +108,8 @@ public class KafkaTestSupport extends AbstractExternalResourceTestSupport<String
|
||||
zookeeper = new EmbeddedZookeeper(TestZKUtils.zookeeperConnect());
|
||||
log.debug("Started Zookeeper at " + zookeeper.getConnectString());
|
||||
try {
|
||||
int zkConnectionTimeout = 6000;
|
||||
int zkSessionTimeout = 6000;
|
||||
int zkConnectionTimeout = 10000;
|
||||
int zkSessionTimeout = 10000;
|
||||
zkClient = new ZkClient(getZkConnectString(), zkSessionTimeout, zkConnectionTimeout, ZKStringSerializer$.MODULE$);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -133,7 +133,7 @@ public class KafkaTestSupport extends AbstractExternalResourceTestSupport<String
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.zkClient = new ZkClient(DEFAULT_ZOOKEEPER_CONNECT, 2000, 2000, ZKStringSerializer$.MODULE$);
|
||||
this.zkClient = new ZkClient(DEFAULT_ZOOKEEPER_CONNECT, 10000, 10000, ZKStringSerializer$.MODULE$);
|
||||
if (ZkUtils.getAllBrokersInCluster(zkClient).size() == 0) {
|
||||
hasFailedAlready = true;
|
||||
throw new RuntimeException("Kafka server not available");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-stream-module-test-support</artifactId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<description>A set of classes to ease testing of Spring Cloud Stream modules.</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
||||
@@ -24,7 +24,6 @@ import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.test.matcher.MessageQueueMatcher;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
@@ -39,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
* </ul>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Gary Russell
|
||||
* @see MessageQueueMatcher
|
||||
*/
|
||||
public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
@@ -51,7 +51,7 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPubSubConsumer(String name, MessageChannel inboundBindTarget, Properties properties) {
|
||||
public void bindPubSubConsumer(String name, MessageChannel inboundBindTarget, String group, Properties properties) {
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPubSubConsumers(String name, String group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindProducers(String name) {
|
||||
|
||||
@@ -116,11 +121,6 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCapable(Capability capability) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public MessageCollector messageCollector() {
|
||||
return messageCollector;
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
*/
|
||||
private static class MessageCollectorImpl implements MessageCollector{
|
||||
|
||||
private Map<MessageChannel, BlockingQueue<Message<?>>> results = new HashMap<>();
|
||||
private final Map<MessageChannel, BlockingQueue<Message<?>>> results = new HashMap<>();
|
||||
|
||||
private BlockingQueue register(MessageChannel channel) {
|
||||
LinkedBlockingDeque<Message<?>> result = new LinkedBlockingDeque<>();
|
||||
@@ -145,6 +145,7 @@ public class TestSupportBinder implements Binder<MessageChannel> {
|
||||
Assert.notNull(results.remove(channel), "Trying to unregister a mapping for an unknown channel [" + channel + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockingQueue<Message<?>> forChannel(MessageChannel channel) {
|
||||
BlockingQueue<Message<?>> queue = results.get(channel);
|
||||
Assert.notNull(queue, "Channel [" + channel + "] was not bound by " + TestSupportBinder.class);
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderUtils;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
|
||||
import org.springframework.cloud.stream.converter.AbstractFromMessageConverter;
|
||||
@@ -55,9 +56,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class ChannelBindingService implements InitializingBean {
|
||||
|
||||
private Binder<MessageChannel> binder;
|
||||
private final Binder<MessageChannel> binder;
|
||||
|
||||
private ChannelBindingServiceProperties channelBindingServiceProperties;
|
||||
private final ChannelBindingServiceProperties channelBindingServiceProperties;
|
||||
|
||||
private CompositeMessageConverterFactory messageConverterFactory;
|
||||
|
||||
@@ -83,9 +84,13 @@ public class ChannelBindingService implements InitializingBean {
|
||||
|
||||
public void bindConsumer(MessageChannel inputChannel, String inputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName);
|
||||
if (isChannelPubSub(channelBindingTarget)) {
|
||||
if (BinderUtils.isChannelPubSub(channelBindingTarget)) {
|
||||
BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindings()
|
||||
.get(inputChannelName);
|
||||
String group = bindingProperties == null ? null : bindingProperties.getGroup();
|
||||
this.binder.bindPubSubConsumer(removePrefix(channelBindingTarget),
|
||||
inputChannel, this.channelBindingServiceProperties.getConsumerProperties(inputChannelName));
|
||||
inputChannel, group,
|
||||
this.channelBindingServiceProperties.getConsumerProperties(inputChannelName));
|
||||
}
|
||||
else {
|
||||
this.binder.bindConsumer(channelBindingTarget, inputChannel,
|
||||
@@ -95,7 +100,7 @@ public class ChannelBindingService implements InitializingBean {
|
||||
|
||||
public void bindProducer(MessageChannel outputChannel, String outputChannelName) {
|
||||
String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName);
|
||||
if (isChannelPubSub(channelBindingTarget)) {
|
||||
if (BinderUtils.isChannelPubSub(channelBindingTarget)) {
|
||||
this.binder.bindPubSubProducer(removePrefix(channelBindingTarget),
|
||||
outputChannel, this.channelBindingServiceProperties.getProducerProperties(outputChannelName));
|
||||
}
|
||||
@@ -105,11 +110,6 @@ public class ChannelBindingService implements InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isChannelPubSub(String bindingTarget) {
|
||||
Assert.isTrue(StringUtils.hasText(bindingTarget), "Binding target should not be empty/null.");
|
||||
return bindingTarget.startsWith("topic:");
|
||||
}
|
||||
|
||||
private String removePrefix(String bindingTarget) {
|
||||
Assert.isTrue(StringUtils.hasText(bindingTarget), "Binding target should not be empty/null.");
|
||||
return bindingTarget.substring(bindingTarget.indexOf(":") + 1);
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@JsonInclude(value = Include.NON_DEFAULT)
|
||||
public class BindingProperties {
|
||||
@@ -42,6 +43,8 @@ public class BindingProperties {
|
||||
|
||||
private String partitionSelectorExpression;
|
||||
|
||||
private String group;
|
||||
|
||||
private String contentType;
|
||||
|
||||
public String getDestination() {
|
||||
@@ -100,6 +103,14 @@ public class BindingProperties {
|
||||
this.partitionSelectorExpression = partitionSelectorExpression;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.stream")
|
||||
@JsonInclude(Include.NON_DEFAULT)
|
||||
@@ -45,7 +46,7 @@ public class ChannelBindingServiceProperties {
|
||||
|
||||
private Properties producerProperties = new Properties();
|
||||
|
||||
private Map<String,BindingProperties> bindings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
private Map<String, BindingProperties> bindings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private Properties getConsumerProperties() {
|
||||
return this.consumerProperties;
|
||||
@@ -173,8 +174,4 @@ public class ChannelBindingServiceProperties {
|
||||
}
|
||||
}
|
||||
|
||||
public String getTapChannelName(String channelName) {
|
||||
return "tap:" + getBindingDestination(channelName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public class BinderAwareChannelResolverTests {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
binder.bindPubSubConsumer("topic:bar", testChannel, null);
|
||||
binder.bindPubSubConsumer("topic:bar", testChannel, null, null);
|
||||
}
|
||||
assertEquals(0, received.size());
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -24,7 +26,6 @@ import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -54,8 +55,9 @@ public class ProcessorBindingTestsWithPubSubBindingTargets {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindPubSubConsumer(eq("testtock.0"), eq(testProcessor.input()), Mockito.<Properties>any());
|
||||
verify(binder).bindPubSubProducer(eq("testtock.1"), eq(testProcessor.output()), Mockito.<Properties>any());
|
||||
verify(binder).bindPubSubConsumer(eq("testtock.0"), eq(testProcessor.input()), anyString(),
|
||||
any(Properties.class));
|
||||
verify(binder).bindPubSubProducer(eq("testtock.1"), eq(testProcessor.output()), any(Properties.class));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.Bindings;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.cloud.stream.utils.MockBinderConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(SinkBindingPubSubTests.TestSink.class)
|
||||
public class SinkBindingPubSubTests {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Autowired
|
||||
private Binder binder;
|
||||
|
||||
@Autowired @Bindings(TestSink.class)
|
||||
private Sink testSink;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSourceOutputChannelBound() {
|
||||
verify(binder).bindPubSubConsumer(eq("testpubsub"), eq(testSink.input()), eq("tgroup"), any(Properties.class));
|
||||
verifyNoMoreInteractions(binder);
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
@Import(MockBinderConfiguration.class)
|
||||
@PropertySource("classpath:/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties")
|
||||
public static class TestSink {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
spring.cloud.stream.bindings.input.destination=topic:testpubsub
|
||||
spring.cloud.stream.bindings.input.group=tgroup
|
||||
Reference in New Issue
Block a user