From 9b0c4bd627f7d122be60d11caf993b18799b3ae6 Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Sun, 6 Mar 2016 20:55:44 -0500 Subject: [PATCH] Typesafe consumer and producer configurations - Change the Binder interface to support ConsumerProperties/ProducerProperties beans and subclasses - Binders can subclass the property beans to add new supported properties that will be automatically populated - Spring Cloud Stream will infer the target type and populate the beans from the environment based on a `spring.cloud.stream.bindings..` prefix - Remove binder defaults and retain only general binder configurations TODO: a) decide on instanceIndex/partitionIndex alignment (we do not need both) b) support `defaultProducer`/`defaultConsumer` properties c) add leniency control on binding (fail/ignore for unknown properties) d) add a `requiredProperties` configuration for consumer/producer properties to finely tune the mandatory properties expected to be supported by a bound application Changes made during review: - Add support for consumer and producer defaults - Remove partitionIndex, keeping only instanceIndex - Fix default properties for Kafka binder - Move batching properties to Rabbit only --- .../binder/kafka/KafkaConsumerProperties.java | 75 +++ .../kafka/KafkaMessageChannelBinder.java | 382 ++++---------- .../binder/kafka/KafkaProducerProperties.java | 76 +++ ...ion.java => KafkaBinderConfiguration.java} | 29 +- .../KafkaBinderConfigurationProperties.java | 92 ++-- .../config/KafkaBinderDefaultProperties.java | 127 ----- .../kafka-binder.properties | 19 +- .../main/resources/META-INF/spring.binders | 2 +- .../stream/binder/kafka/KafkaBinderTests.java | 210 +++----- .../stream/binder/kafka/KafkaTestBinder.java | 10 +- .../binder/kafka/RawModeKafkaBinderTests.java | 87 ++-- .../rabbit/RabbitConsumerProperties.java | 148 ++++++ .../rabbit/RabbitMessageChannelBinder.java | 464 +++--------------- .../rabbit/RabbitProducerProperties.java | 127 +++++ .../RabbitBinderConfigurationProperties.java | 235 ++------- ...bbitMessageChannelBinderConfiguration.java | 48 +- .../RabbitServiceAutoConfiguration.java | 2 - .../config/SpringRabbitMQProperties.java | 107 ---- .../rabbit-binder.properties | 23 - .../binder/rabbit/RabbitBinderTests.java | 310 ++++++------ .../binder/rabbit/RabbitTestBinder.java | 28 +- .../integration/RabbitBinderModuleTests.java | 6 +- .../redis/RedisMessageChannelBinder.java | 93 ++-- ...edisMessageChannelBinderConfiguration.java | 9 - .../stream/binder/redis/RedisBinderTests.java | 78 +-- .../stream/binder/redis/RedisTestBinder.java | 4 +- .../integration/RedisBinderModuleTests.java | 6 +- .../stream/binder/AbstractBinderTests.java | 50 +- .../stream/binder/AbstractTestBinder.java | 7 +- .../stream/binder/BrokerBinderTests.java | 4 +- .../binder/PartitionCapableBinderTests.java | 121 ++--- .../MessageChannelBinderSupportTests.java | 7 +- .../config/MessageChannelConfigurerTests.java | 1 - .../stream/test/binder/TestSupportBinder.java | 9 +- .../TestSupportBinderAutoConfiguration.java | 2 +- .../stream/annotation/EnableBinding.java | 4 +- .../cloud/stream/binder/AbstractBinder.java | 291 +---------- .../cloud/stream/binder/Binder.java | 13 +- .../cloud/stream/binder/BinderFactory.java | 2 +- .../stream/binder/BinderPropertyKeys.java | 140 ------ .../stream/binder/ConsumerProperties.java | 99 ++-- .../stream/binder/DefaultBinderFactory.java | 10 +- .../cloud/stream/binder/DefaultBinding.java | 10 +- .../DefaultBindingPropertiesAccessor.java | 374 -------------- .../cloud/stream/binder/PartitionHandler.java | 87 +--- .../stream/binder/ProducerProperties.java | 92 ++++ .../binding/BinderAwareChannelResolver.java | 47 +- .../stream/binding/ChannelBindingService.java | 74 ++- .../binding/MessageConverterConfigurer.java | 2 +- .../MessageHistoryTrackerConfigurer.java | 2 +- .../stream/config/BindingProperties.java | 224 +-------- .../config/BindingPropertiesConverter.java | 39 -- .../ChannelBindingServiceConfiguration.java | 7 - .../ChannelBindingServiceProperties.java | 237 ++++----- .../SpelExpressionConverterConfiguration.java | 3 +- .../stream/endpoint/ChannelsEndpoint.java | 14 +- ...terfaceBindingTestsWithBindingTargets.java | 10 +- ...raryInterfaceBindingTestsWithDefaults.java | 8 +- .../BinderAwareChannelResolverTests.java | 42 +- .../BinderFactoryConfigurationTests.java | 5 +- .../stream/binder/DefaultSettingsTests.java | 130 +++++ .../stream/binder/ErrorBindingTests.java | 14 +- .../binder/InputOutputBindingOrderTest.java | 6 +- ...ocessorBindingTestsWithBindingTargets.java | 4 +- .../ProcessorBindingTestsWithDefaults.java | 4 +- .../SinkBindingTestsWithBindingTargets.java | 2 +- .../binder/SinkBindingTestsWithDefaults.java | 2 +- .../SourceBindingTestsWithBindingTargets.java | 3 +- .../SourceBindingTestsWithDefaults.java | 4 +- .../stream/binder/stub1/StubBinder1.java | 10 +- .../stub1/StubBinder1Configuration.java | 2 +- .../stream/binder/stub2/StubBinder2.java | 10 +- .../stub2/StubBinder2ConfigurationA.java | 2 +- .../binding/ChannelBindingServiceTests.java | 33 +- .../PropertiesClassResolutionTests.java | 161 ++++++ ...ExpressionConverterConfigurationTests.java | 13 +- .../partitioning/PartitionedConsumerTest.java | 17 +- .../partitioning/PartitionedProducerTest.java | 10 +- .../stream/utils/MockBinderConfiguration.java | 2 +- .../MockBinderRegistryConfiguration.java | 2 +- .../binder/arbitrary-binding-test.properties | 8 +- .../processor-binding-test-pubsub.properties | 2 - .../binder/processor-binding-test.properties | 4 +- .../sink-binding-pubsub-test.properties | 2 - 84 files changed, 1994 insertions(+), 3287 deletions(-) create mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java create mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaProducerProperties.java rename spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/{KafkaServiceAutoConfiguration.java => KafkaBinderConfiguration.java} (74%) delete mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderDefaultProperties.java create mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java create mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java delete mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/SpringRabbitMQProperties.java delete mode 100644 spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring-cloud-stream/rabbit-binder.properties delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderPropertyKeys.java rename spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisBinderConfigurationProperties.java => spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java (60%) delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBindingPropertiesAccessor.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingPropertiesConverter.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/DefaultSettingsTests.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/PropertiesClassResolutionTests.java delete mode 100644 spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test-pubsub.properties delete mode 100644 spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java new file mode 100644 index 000000000..848bbc526 --- /dev/null +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaConsumerProperties.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder.kafka; + +import org.springframework.cloud.stream.binder.ConsumerProperties; + +/** + * @author Marius Bogoevici + */ +public class KafkaConsumerProperties extends ConsumerProperties { + + private int minPartitionCount = 1; + + private boolean autoCommitOffset = true; + + private boolean resetOffsets = false; + + private KafkaMessageChannelBinder.StartOffset startOffset = null; + + private KafkaMessageChannelBinder.Mode mode = KafkaMessageChannelBinder.Mode.embeddedHeaders; + + public void setMinPartitionCount(int minPartitionCount) { + this.minPartitionCount = minPartitionCount; + } + + public int getMinPartitionCount() { + return minPartitionCount; + } + + public boolean isAutoCommitOffset() { + return autoCommitOffset; + } + + public void setAutoCommitOffset(boolean autoCommitOffset) { + this.autoCommitOffset = autoCommitOffset; + } + + public KafkaMessageChannelBinder.Mode getMode() { + return mode; + } + + public void setMode(KafkaMessageChannelBinder.Mode mode) { + this.mode = mode; + } + + public boolean isResetOffsets() { + return resetOffsets; + } + + public void setResetOffsets(boolean resetOffsets) { + this.resetOffsets = resetOffsets; + } + + public KafkaMessageChannelBinder.StartOffset getStartOffset() { + return startOffset; + } + + public void setStartOffset(KafkaMessageChannelBinder.StartOffset startOffset) { + this.startOffset = startOffset; + } +} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java index a0b67975f..2f393e21f 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java @@ -22,18 +22,23 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; +import kafka.admin.AdminUtils; +import kafka.api.OffsetRequest; +import kafka.serializer.Decoder; +import kafka.serializer.DefaultDecoder; +import kafka.utils.ZKStringSerializer$; +import kafka.utils.ZkUtils; import org.I0Itec.zkclient.ZkClient; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.ByteArraySerializer; +import scala.collection.Seq; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -41,10 +46,8 @@ import org.springframework.cloud.stream.binder.AbstractBinder; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderException; import org.springframework.cloud.stream.binder.BinderHeaders; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.DefaultBinding; -import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor; import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter; import org.springframework.cloud.stream.binder.MessageValues; import org.springframework.cloud.stream.binder.PartitionHandler; @@ -84,14 +87,6 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import kafka.admin.AdminUtils; -import kafka.api.OffsetRequest; -import kafka.serializer.Decoder; -import kafka.serializer.DefaultDecoder; -import kafka.utils.ZKStringSerializer$; -import kafka.utils.ZkUtils; -import scala.collection.Seq; - /** * A {@link Binder} that uses Kafka as the underlying middleware. * @@ -103,79 +98,10 @@ import scala.collection.Seq; * @author Mark Fisher * @author Soby Chacko */ -public class KafkaMessageChannelBinder extends AbstractBinder { +public class KafkaMessageChannelBinder extends AbstractBinder { public static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer(); - public static final int METADATA_VERIFICATION_RETRY_ATTEMPTS = 10; - - public static final double METADATA_VERIFICATION_RETRY_BACKOFF_MULTIPLIER = 2; - - public static final int METADATA_VERIFICATION_RETRY_INITIAL_INTERVAL = 100; - - public static final int METADATA_VERIFICATION_MAX_INTERVAL = 1000; - - public static final String FETCH_SIZE = "fetchSize"; - - public static final String QUEUE_SIZE = "fetchSize"; - - public static final String REQUIRED_ACKS = "requiredAcks"; - - public static final String COMPRESSION_CODEC = "compressionCodec"; - - public static final String AUTO_COMMIT_ENABLED = "autoCommitEnabled"; - - private static final String DEFAULT_COMPRESSION_CODEC = "none"; - - private static final int DEFAULT_REQUIRED_ACKS = 1; - - private static final boolean DEFAULT_AUTO_COMMIT_ENABLED = true; - - private static final boolean DEFAULT_RESET_OFFSETS = false; - - private static final int DEFAULT_ZK_SESSION_TIMEOUT = 10000; - - private static final int DEFAULT_ZK_CONNECTION_TIMEOUT = 10000; - - private static final boolean DEFAULT_SYNC_PRODUCER = false; - - protected static final Set PRODUCER_COMPRESSION_PROPERTIES = new HashSet( - Arrays.asList(new String[] { - KafkaMessageChannelBinder.COMPRESSION_CODEC, - })); - - private static final Set KAFKA_CONSUMER_PROPERTIES = new SetBuilder() - .add(BinderPropertyKeys.MIN_PARTITION_COUNT) - .build(); - - /** - * Basic + concurrency + partitioning. - */ - private static final Set SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder() - .addAll(CONSUMER_STANDARD_PROPERTIES) - .addAll(KAFKA_CONSUMER_PROPERTIES) - .add(BinderPropertyKeys.PARTITION_INDEX) // Not actually used - .add(BinderPropertyKeys.COUNT) // Not actually used - .add(BinderPropertyKeys.CONCURRENCY) - .add(FETCH_SIZE) - .build(); - - private static final Set KAFKA_PRODUCER_PROPERTIES = new SetBuilder() - .add(BinderPropertyKeys.MIN_PARTITION_COUNT) - .add(BinderPropertyKeys.REQUIRED_GROUPS) - .build(); - - /** - * Partitioning + kafka producer properties. - */ - private static final Set SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder() - .addAll(PRODUCER_PARTITIONING_PROPERTIES) - .addAll(PRODUCER_STANDARD_PROPERTIES) - .addAll(KAFKA_PRODUCER_PROPERTIES) - .addAll(PRODUCER_BATCHING_BASIC_PROPERTIES) - .addAll(PRODUCER_COMPRESSION_PROPERTIES) - .build(); - private RetryOperations retryOperations; private final Map> topicsInUse = new HashMap<>(); @@ -193,26 +119,20 @@ public class KafkaMessageChannelBinder extends AbstractBinder { // -------- Default values for properties ------- - private int defaultReplicationFactor = 1; + private int replicationFactor = 1; - private String defaultCompressionCodec = DEFAULT_COMPRESSION_CODEC; + private int requiredAcks = 1; - private int defaultRequiredAcks = DEFAULT_REQUIRED_ACKS; + private int queueSize = 1024; - private int defaultQueueSize = 1024; + private int maxWait = 100; - private int defaultMaxWait = 100; - - private int defaultFetchSize = 1024 * 1024; + private int fetchSize = 1024 * 1024; private int defaultMinPartitionCount = 1; private ConnectionFactory connectionFactory; - // auto commit property - - private boolean defaultAutoCommitEnabled = DEFAULT_AUTO_COMMIT_ENABLED; - private int socketBufferSize = 2097152; private int offsetUpdateTimeWindow = 10000; @@ -221,22 +141,14 @@ public class KafkaMessageChannelBinder extends AbstractBinder { private int offsetUpdateShutdownTimeout = 2000; - private Mode mode = Mode.embeddedHeaders; + private int zkSessionTimeout = 10000; - private boolean resetOffsets = DEFAULT_RESET_OFFSETS; - - private StartOffset startOffset = null; - - private int zkSessionTimeout = DEFAULT_ZK_SESSION_TIMEOUT; - - private int zkConnectionTimeout = DEFAULT_ZK_CONNECTION_TIMEOUT; - - private boolean syncProducer = DEFAULT_SYNC_PRODUCER; + private int zkConnectionTimeout = 10000; private ProducerListener producerListener; public KafkaMessageChannelBinder(ZookeeperConnect zookeeperConnect, String brokers, String zkAddress, - String... headersToMap) { + String... headersToMap) { this.zookeeperConnect = zookeeperConnect; this.brokers = brokers; this.zkAddress = zkAddress; @@ -273,14 +185,6 @@ public class KafkaMessageChannelBinder extends AbstractBinder { this.offsetUpdateShutdownTimeout = offsetUpdateShutdownTimeout; } - public boolean isSyncProducer() { - return this.syncProducer; - } - - public void setSyncProducer(boolean syncProducer) { - this.syncProducer = syncProducer; - } - public ConnectionFactory getConnectionFactory() { return connectionFactory; } @@ -301,7 +205,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder { public void onInit() throws Exception { ZookeeperConfiguration configuration = new ZookeeperConfiguration(this.zookeeperConnect); configuration.setBufferSize(socketBufferSize); - configuration.setMaxWait(defaultMaxWait); + configuration.setMaxWait(maxWait); DefaultConnectionFactory defaultConnectionFactory = new DefaultConnectionFactory(configuration); defaultConnectionFactory.afterPropertiesSet(); @@ -310,13 +214,13 @@ public class KafkaMessageChannelBinder extends AbstractBinder { RetryTemplate retryTemplate = new RetryTemplate(); SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(); - simpleRetryPolicy.setMaxAttempts(METADATA_VERIFICATION_RETRY_ATTEMPTS); + simpleRetryPolicy.setMaxAttempts(10); retryTemplate.setRetryPolicy(simpleRetryPolicy); ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); - backOffPolicy.setInitialInterval(METADATA_VERIFICATION_RETRY_INITIAL_INTERVAL); - backOffPolicy.setMultiplier(METADATA_VERIFICATION_RETRY_BACKOFF_MULTIPLIER); - backOffPolicy.setMaxInterval(METADATA_VERIFICATION_MAX_INTERVAL); + backOffPolicy.setInitialInterval(100); + backOffPolicy.setMultiplier((double) 2); + backOffPolicy.setMaxInterval(1000); retryTemplate.setBackOffPolicy(backOffPolicy); retryOperations = retryTemplate; } @@ -340,61 +244,28 @@ public class KafkaMessageChannelBinder extends AbstractBinder { } } - public void setDefaultReplicationFactor(int defaultReplicationFactor) { - this.defaultReplicationFactor = defaultReplicationFactor; + public void setReplicationFactor(int replicationFactor) { + this.replicationFactor = replicationFactor; } - public void setDefaultCompressionCodec(String defaultCompressionCodec) { - this.defaultCompressionCodec = defaultCompressionCodec; + public void setRequiredAcks(int requiredAcks) { + this.requiredAcks = requiredAcks; } - public void setDefaultRequiredAcks(int defaultRequiredAcks) { - this.defaultRequiredAcks = defaultRequiredAcks; + public void setQueueSize(int queueSize) { + this.queueSize = queueSize; } - /** - * Set the default auto commit enabled property; This is used to commit the offset either automatically or - * manually. - * @param defaultAutoCommitEnabled - */ - public void setDefaultAutoCommitEnabled(boolean defaultAutoCommitEnabled) { - this.defaultAutoCommitEnabled = defaultAutoCommitEnabled; - } - - public void setDefaultQueueSize(int defaultQueueSize) { - this.defaultQueueSize = defaultQueueSize; - } - - public void setDefaultFetchSize(int defaultFetchSize) { - this.defaultFetchSize = defaultFetchSize; + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; } public void setDefaultMinPartitionCount(int defaultMinPartitionCount) { this.defaultMinPartitionCount = defaultMinPartitionCount; } - public void setDefaultMaxWait(int defaultMaxWait) { - this.defaultMaxWait = defaultMaxWait; - } - - public void setMode(Mode mode) { - this.mode = mode; - } - - public boolean isResetOffsets() { - return resetOffsets; - } - - public void setResetOffsets(boolean resetOffsets) { - this.resetOffsets = resetOffsets; - } - - public StartOffset getStartOffset() { - return startOffset; - } - - public void setStartOffset(StartOffset startOffset) { - this.startOffset = startOffset; + public void setMaxWait(int maxWait) { + this.maxWait = maxWait; } public int getZkSessionTimeout() { @@ -418,7 +289,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder { } @Override - protected Binding doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) { + protected Binding doBindConsumer(String name, String group, MessageChannel inputChannel, KafkaConsumerProperties properties) { // If the caller provides a consumer group, use it; otherwise an anonymous consumer group // is generated each time, such that each anonymous binding will receive all messages. // Consumers reset offsets at the latest time by default, which allows them to receive only @@ -429,57 +300,49 @@ public class KafkaMessageChannelBinder extends AbstractBinder { // The reference point, if not set explicitly is the latest time for anonymous subscriptions and the // earliest time for group subscriptions. This allows the latter to receive messages published before the group // has been created. - long referencePoint = this.startOffset != null ? - startOffset.getReferencePoint() : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime()); + long referencePoint = properties.getStartOffset() != null ? + properties.getStartOffset().getReferencePoint() : (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime()); return createKafkaConsumer(name, inputChannel, properties, consumerGroup, referencePoint); } + @Override - public Binding bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) { + public Binding doBindProducer(String name, MessageChannel moduleOutputChannel, KafkaProducerProperties properties) { + Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel); - KafkaPropertiesAccessor producerPropertiesAccessor = new KafkaPropertiesAccessor(properties); - validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES); if (logger.isInfoEnabled()) { logger.info("Using kafka topic for outbound: " + name); } validateTopicName(name); - int numPartitions = producerPropertiesAccessor.getNumberOfKafkaPartitionsForProducer(); - - Collection partitions = ensureTopicCreated(name, numPartitions, defaultReplicationFactor); + int numPartitions = Math.max(defaultMinPartitionCount, properties.getPartitionCount()); + Collection partitions = ensureTopicCreated(name, numPartitions, replicationFactor); topicsInUse.put(name, partitions); - ProducerMetadata producerMetadata = new ProducerMetadata<>( - name, byte[].class, byte[].class, BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER); - producerMetadata.setSync(isSyncProducer()); - producerMetadata.setCompressionType(ProducerMetadata.CompressionType.valueOf( - producerPropertiesAccessor.getCompressionCodec(this.defaultCompressionCodec))); - producerMetadata.setBatchBytes(producerPropertiesAccessor.getBatchSize(this.defaultBatchSize)); + ProducerMetadata producerMetadata = new ProducerMetadata<>(name, byte[].class, byte[].class, + BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER); + producerMetadata.setSync(properties.isSync()); + producerMetadata.setCompressionType(properties.getCompressionType()); + producerMetadata.setBatchBytes(properties.getBufferSize()); Properties additionalProps = new Properties(); - additionalProps.put(ProducerConfig.ACKS_CONFIG, - String.valueOf(producerPropertiesAccessor.getRequiredAcks(this - .defaultRequiredAcks))); - additionalProps.put(ProducerConfig.LINGER_MS_CONFIG, - String.valueOf(producerPropertiesAccessor.getBatchTimeout(this - .defaultBatchTimeout))); - ProducerFactoryBean producerFB = - new ProducerFactoryBean<>(producerMetadata, brokers, additionalProps); + additionalProps.put(ProducerConfig.ACKS_CONFIG, String.valueOf(requiredAcks)); + additionalProps.put(ProducerConfig.LINGER_MS_CONFIG, String.valueOf(properties.getBatchTimeout())); + ProducerFactoryBean producerFB = new ProducerFactoryBean<>(producerMetadata, brokers, additionalProps); try { final ProducerConfiguration producerConfiguration = new ProducerConfiguration<>( producerMetadata, producerFB.getObject()); producerConfiguration.setProducerListener(producerListener); - MessageHandler handler = new SendingHandler(name, producerPropertiesAccessor, - partitions.size(), producerConfiguration); + MessageHandler handler = new SendingHandler(name, properties, partitions.size(), producerConfiguration); EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler); consumer.setBeanFactory(this.getBeanFactory()); consumer.setBeanName("outbound." + name); consumer.afterPropertiesSet(); - DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, producerPropertiesAccessor); + DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer); consumer.start(); return producerBinding; } @@ -542,62 +405,44 @@ public class KafkaMessageChannelBinder extends AbstractBinder { } } - private Binding createKafkaConsumer(String name, final MessageChannel moduleInputChannel, Properties properties, - String group, long referencePoint) { - - validateConsumerProperties(groupedName(name, group), properties, SUPPORTED_CONSUMER_PROPERTIES); - KafkaPropertiesAccessor accessor = new KafkaPropertiesAccessor(properties); - - int maxConcurrency = accessor.getConcurrency(defaultConcurrency); + private Binding createKafkaConsumer(String name, final MessageChannel moduleInputChannel, + KafkaConsumerProperties properties, String group, long referencePoint) { validateTopicName(name); - - int numPartitions = accessor.getNumberOfKafkaPartitionsForConsumer(); - Collection allPartitions = ensureTopicCreated(name, numPartitions, defaultReplicationFactor); + int minKafkaPartitions = properties.getMinPartitionCount(); + int instance = properties.getInstanceCount(); + if (instance == 0) { + throw new IllegalArgumentException("Instance count cannot be zero"); + } + int numPartitions = Math.max(minKafkaPartitions, instance * properties.getConcurrency()); + Collection allPartitions = ensureTopicCreated(name, numPartitions, replicationFactor); Decoder valueDecoder = new DefaultDecoder(null); Decoder keyDecoder = new DefaultDecoder(null); Collection listenedPartitions; - int moduleCount = accessor.getCount(); - - if (moduleCount == 1) { + if (instance == 1) { listenedPartitions = allPartitions; } else { - listenedPartitions = new ArrayList(); + listenedPartitions = new ArrayList<>(); for (Partition partition : allPartitions) { // divide partitions across modules - if (accessor.getPartitionIndex() != -1) { - if ((partition.getId() % moduleCount) == accessor.getPartitionIndex()) { - listenedPartitions.add(partition); - } - } - else { - int moduleSequence = accessor.getSequence(); - if (moduleCount == 0) { - throw new IllegalArgumentException("The Kafka transport does not support 0-count modules"); - } - else { - // sequence numbers are zero-based - if ((partition.getId() % moduleCount) == (moduleSequence - 1)) { - listenedPartitions.add(partition); - } - } + if ((partition.getId() % instance) == properties.getInstanceIndex()) { + listenedPartitions.add(partition); } } } topicsInUse.put(name, listenedPartitions); - ReceivingHandler rh = new ReceivingHandler(); + ReceivingHandler rh = new ReceivingHandler(properties); rh.setOutputChannel(moduleInputChannel); final FixedSubscriberChannel bridge = new FixedSubscriberChannel(rh); bridge.setBeanName("bridge." + name); final KafkaMessageListenerContainer messageListenerContainer = - createMessageListenerContainer(accessor, group, maxConcurrency, listenedPartitions, - referencePoint); + createMessageListenerContainer(properties, group, null, listenedPartitions, referencePoint); final KafkaMessageDrivenChannelAdapter kafkaMessageDrivenChannelAdapter = new KafkaMessageDrivenChannelAdapter(messageListenerContainer); @@ -605,8 +450,7 @@ public class KafkaMessageChannelBinder extends AbstractBinder { kafkaMessageDrivenChannelAdapter.setKeyDecoder(keyDecoder); kafkaMessageDrivenChannelAdapter.setPayloadDecoder(valueDecoder); kafkaMessageDrivenChannelAdapter.setOutputChannel(bridge); - kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(accessor.getDefaultAutoCommitEnabled(this - .defaultAutoCommitEnabled)); + kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(properties.isAutoCommitOffset()); kafkaMessageDrivenChannelAdapter.afterPropertiesSet(); kafkaMessageDrivenChannelAdapter.start(); @@ -632,25 +476,14 @@ public class KafkaMessageChannelBinder extends AbstractBinder { String groupedName = groupedName(name, group); edc.setBeanName("inbound." + groupedName); - DefaultBinding consumerBinding = new DefaultBinding<>(name, group, moduleInputChannel, edc, accessor); + DefaultBinding consumerBinding = new DefaultBinding<>(name, group, moduleInputChannel, edc); edc.start(); return consumerBinding; } - public KafkaMessageListenerContainer createMessageListenerContainer(Properties properties, String group, - int maxConcurrency, String topic, long referencePoint) { - return createMessageListenerContainer(new KafkaPropertiesAccessor(properties), group, maxConcurrency, topic, - null, referencePoint); - } - - private KafkaMessageListenerContainer createMessageListenerContainer(KafkaPropertiesAccessor accessor, - String group, int maxConcurrency, Collection listenedPartitions, long referencePoint) { - return createMessageListenerContainer(accessor, group, maxConcurrency, null, listenedPartitions, referencePoint); - } - - private KafkaMessageListenerContainer createMessageListenerContainer(KafkaPropertiesAccessor accessor, - String group, int maxConcurrency, String topic, Collection listenedPartitions, - long referencePoint) { + KafkaMessageListenerContainer createMessageListenerContainer(KafkaConsumerProperties consumerProperties, + String group, String topic, Collection listenedPartitions, + long referencePoint) { Assert.isTrue(StringUtils.hasText(topic) ^ !CollectionUtils.isEmpty(listenedPartitions), "Exactly one of topic or a list of listened partitions must be provided"); KafkaMessageListenerContainer messageListenerContainer; @@ -664,15 +497,15 @@ public class KafkaMessageChannelBinder extends AbstractBinder { if (logger.isDebugEnabled()) { logger.debug("Listening to topic " + topic); } - // if we have less target partitions than target concurrency, adjust accordingly - messageListenerContainer.setConcurrency(Math.min(maxConcurrency, listenedPartitions.size())); + // if we have fewer target partitions than target concurrency, adjust accordingly + messageListenerContainer.setConcurrency(Math.min(consumerProperties.getConcurrency(), listenedPartitions.size())); OffsetManager offsetManager = createOffsetManager(group, referencePoint); - if (resetOffsets) { + if (consumerProperties.isResetOffsets()) { offsetManager.resetOffsets(listenedPartitions); } messageListenerContainer.setOffsetManager(offsetManager); - messageListenerContainer.setQueueSize(accessor.getProperty(QUEUE_SIZE, defaultQueueSize)); - messageListenerContainer.setMaxFetch(accessor.getProperty(FETCH_SIZE, defaultFetchSize)); + messageListenerContainer.setQueueSize(queueSize); + messageListenerContainer.setMaxFetch(fetchSize); return messageListenerContainer; } @@ -711,60 +544,18 @@ public class KafkaMessageChannelBinder extends AbstractBinder { } } - private class KafkaPropertiesAccessor extends DefaultBindingPropertiesAccessor { - - public KafkaPropertiesAccessor(Properties properties) { - super(properties); - } - - public int getNumberOfKafkaPartitionsForProducer() { - int nextModuleCount = getNextModuleCount(); - if (nextModuleCount == 0) { - throw new IllegalArgumentException("Module count cannot be zero"); - } - int nextModuleConcurrency = getProperty(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, defaultConcurrency); - int minKafkaPartitions = getMinPartitionCount(defaultMinPartitionCount); - return Math.max(minKafkaPartitions, nextModuleCount * nextModuleConcurrency); - } - - public int getNumberOfKafkaPartitionsForConsumer() { - int concurrency = getConcurrency(defaultConcurrency); - int minKafkaPartitions = getMinPartitionCount(defaultMinPartitionCount); - int moduleCount = getCount(); - if (moduleCount == 0) { - throw new IllegalArgumentException("Module count cannot be zero"); - } - return Math.max(minKafkaPartitions, moduleCount * concurrency); - } - - public String getCompressionCodec(String defaultValue) { - return getProperty(COMPRESSION_CODEC, defaultValue); - } - - public int getRequiredAcks(int defaultRequiredAcks) { - return getProperty(REQUIRED_ACKS, defaultRequiredAcks); - } - - public boolean getDefaultAutoCommitEnabled(boolean defaultAutoCommitEnabled) { - return getProperty(AUTO_COMMIT_ENABLED, defaultAutoCommitEnabled); - } - - public int getMinPartitionCount(int defaultPartitionCount) { - return getProperty(BinderPropertyKeys.MIN_PARTITION_COUNT, defaultPartitionCount); - } - - } - private class ReceivingHandler extends AbstractReplyProducingMessageHandler { - public ReceivingHandler() { - this.setBeanFactory(KafkaMessageChannelBinder.this.getBeanFactory()); + private KafkaConsumerProperties consumerProperties; + + public ReceivingHandler(KafkaConsumerProperties consumerProperties) { + this.consumerProperties = consumerProperties; } @Override @SuppressWarnings("unchecked") protected Object handleRequestMessage(Message requestMessage) { - if (Mode.embeddedHeaders.equals(mode)) { + if (Mode.embeddedHeaders.equals(consumerProperties.getMode())) { MessageValues messageValues; try { messageValues = embeddedHeadersMessageConverter.extractHeaders((Message) requestMessage, @@ -805,40 +596,43 @@ public class KafkaMessageChannelBinder extends AbstractBinder { private final String topicName; + private final KafkaProducerProperties producerProperties; + private final int numberOfKafkaPartitions; private final ProducerConfiguration producerConfiguration; private final PartitionHandler partitionHandler; - private SendingHandler(String topicName, KafkaPropertiesAccessor properties, int numberOfPartitions, - ProducerConfiguration producerConfiguration) { + private SendingHandler(String topicName, KafkaProducerProperties properties, int numberOfPartitions, + ProducerConfiguration producerConfiguration) { this.topicName = topicName; + producerProperties = properties; this.numberOfKafkaPartitions = numberOfPartitions; ConfigurableListableBeanFactory beanFactory = KafkaMessageChannelBinder.this.getBeanFactory(); this.setBeanFactory(beanFactory); this.producerConfiguration = producerConfiguration; this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, - properties, numberOfPartitions); + properties); } @Override protected void handleMessageInternal(Message message) throws Exception { int targetPartition; - if (this.partitionHandler.isPartitionedModule()) { + if (producerProperties.isPartitioned()) { targetPartition = this.partitionHandler.determinePartition(message); } else { targetPartition = roundRobin() % numberOfKafkaPartitions; } - if (Mode.embeddedHeaders.equals(mode)) { + if (Mode.embeddedHeaders.equals(producerProperties.getMode())) { MessageValues transformed = serializePayloadIfNecessary(message); byte[] messageToSend = embeddedHeadersMessageConverter.embedHeaders(transformed, KafkaMessageChannelBinder.this.headersToMap); producerConfiguration.send(topicName, targetPartition, null, messageToSend); } - else if (Mode.raw.equals(mode)) { + else if (Mode.raw.equals(producerProperties.getMode())) { Object contentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE); if (contentType != null && !contentType.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE)) { diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaProducerProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaProducerProperties.java new file mode 100644 index 000000000..9447df3b4 --- /dev/null +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaProducerProperties.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder.kafka; + +import org.springframework.cloud.stream.binder.ProducerProperties; +import org.springframework.integration.kafka.support.ProducerMetadata; + +/** + * @author Marius Bogoevici + */ +public class KafkaProducerProperties extends ProducerProperties { + + private int bufferSize = 16384; + + private ProducerMetadata.CompressionType compressionType = ProducerMetadata.CompressionType.none; + + private boolean sync = false; + + private KafkaMessageChannelBinder.Mode mode = KafkaMessageChannelBinder.Mode.embeddedHeaders; + + private int batchTimeout = 0; + + public int getBufferSize() { + return bufferSize; + } + + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + public ProducerMetadata.CompressionType getCompressionType() { + return compressionType; + } + + public void setCompressionType(ProducerMetadata.CompressionType compressionType) { + this.compressionType = compressionType; + } + + public boolean isSync() { + return sync; + } + + public void setSync(boolean sync) { + this.sync = sync; + } + + public KafkaMessageChannelBinder.Mode getMode() { + return mode; + } + + public void setMode(KafkaMessageChannelBinder.Mode mode) { + this.mode = mode; + } + + public int getBatchTimeout() { + return batchTimeout; + } + + public void setBatchTimeout(int batchTimeout) { + this.batchTimeout = batchTimeout; + } +} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaServiceAutoConfiguration.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfiguration.java similarity index 74% rename from spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaServiceAutoConfiguration.java rename to spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfiguration.java index 9c8a2c5d3..d77f912e8 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaServiceAutoConfiguration.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfiguration.java @@ -44,16 +44,13 @@ import org.springframework.util.ObjectUtils; @Configuration @ConditionalOnMissingBean(Binder.class) @Import({KryoCodecAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) -@EnableConfigurationProperties({KafkaBinderConfigurationProperties.class, KafkaBinderDefaultProperties.class}) +@EnableConfigurationProperties({KafkaBinderConfigurationProperties.class}) @PropertySource("classpath:/META-INF/spring-cloud-stream/kafka-binder.properties") -public class KafkaServiceAutoConfiguration { +public class KafkaBinderConfiguration { @Autowired private Codec codec; - @Autowired - private KafkaBinderDefaultProperties kafkaBinderDefaultProperties; - @Autowired private KafkaBinderConfigurationProperties kafkaBinderConfigurationProperties; @@ -77,29 +74,19 @@ public class KafkaServiceAutoConfiguration { : new KafkaMessageChannelBinder(zookeeperConnect(), kafkaConnectionString, zkConnectionString, headers); kafkaMessageChannelBinder.setCodec(codec); - kafkaMessageChannelBinder.setMode(kafkaBinderConfigurationProperties.getMode()); kafkaMessageChannelBinder.setOffsetUpdateTimeWindow(kafkaBinderConfigurationProperties.getOffsetUpdateTimeWindow()); kafkaMessageChannelBinder.setOffsetUpdateCount(kafkaBinderConfigurationProperties.getOffsetUpdateCount()); kafkaMessageChannelBinder.setOffsetUpdateShutdownTimeout(kafkaBinderConfigurationProperties.getOffsetUpdateShutdownTimeout()); - kafkaMessageChannelBinder.setResetOffsets(kafkaBinderConfigurationProperties.isResetOffsets()); - kafkaMessageChannelBinder.setStartOffset(kafkaBinderConfigurationProperties.getStartOffset()); - kafkaMessageChannelBinder.setZkSessionTimeout(kafkaBinderConfigurationProperties.getZkSessionTimeout()); kafkaMessageChannelBinder.setZkConnectionTimeout(kafkaBinderConfigurationProperties.getZkConnectionTimeout()); - kafkaMessageChannelBinder.setSyncProducer(kafkaBinderConfigurationProperties.isSyncProducer()); - - kafkaMessageChannelBinder.setDefaultAutoCommitEnabled(kafkaBinderDefaultProperties.isAutoCommitEnabled()); - kafkaMessageChannelBinder.setDefaultBatchSize(kafkaBinderDefaultProperties.getBatchSize()); - kafkaMessageChannelBinder.setDefaultBatchTimeout(kafkaBinderDefaultProperties.getBatchTimeout()); - kafkaMessageChannelBinder.setDefaultCompressionCodec(kafkaBinderDefaultProperties.getCompressionCodec()); - kafkaMessageChannelBinder.setDefaultConcurrency(kafkaBinderDefaultProperties.getConcurrency()); - kafkaMessageChannelBinder.setDefaultFetchSize(kafkaBinderDefaultProperties.getFetchSize()); - kafkaMessageChannelBinder.setDefaultMinPartitionCount(kafkaBinderDefaultProperties.getMinPartitionCount()); - kafkaMessageChannelBinder.setDefaultQueueSize(kafkaBinderDefaultProperties.getQueueSize()); - kafkaMessageChannelBinder.setDefaultReplicationFactor(kafkaBinderDefaultProperties.getReplicationFactor()); - kafkaMessageChannelBinder.setDefaultRequiredAcks(kafkaBinderDefaultProperties.getRequiredAcks()); + kafkaMessageChannelBinder.setFetchSize(kafkaBinderConfigurationProperties.getFetchSize()); + kafkaMessageChannelBinder.setDefaultMinPartitionCount(kafkaBinderConfigurationProperties.getMinPartitionCount()); + kafkaMessageChannelBinder.setQueueSize(kafkaBinderConfigurationProperties.getQueueSize()); + kafkaMessageChannelBinder.setReplicationFactor(kafkaBinderConfigurationProperties.getReplicationFactor()); + kafkaMessageChannelBinder.setRequiredAcks(kafkaBinderConfigurationProperties.getRequiredAcks()); + kafkaMessageChannelBinder.setMaxWait(kafkaBinderConfigurationProperties.getMaxWait()); kafkaMessageChannelBinder.setProducerListener(producerListener); return kafkaMessageChannelBinder; diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfigurationProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfigurationProperties.java index 1be2b5cf9..683c6d6bb 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfigurationProperties.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderConfigurationProperties.java @@ -47,10 +47,7 @@ class KafkaBinderConfigurationProperties { private int offsetUpdateShutdownTimeout; - private boolean resetOffsets = false; - - private KafkaMessageChannelBinder.StartOffset startOffset; - + private int maxWait = 100; /** * ZK session timeout in milliseconds. */ @@ -61,10 +58,15 @@ class KafkaBinderConfigurationProperties { */ private int zkConnectionTimeout; - /** - * Flag to indicate if the Kafka Producer is synchronous or asynchronous. - */ - private boolean syncProducer = false; + private int requiredAcks = 1; + + private int replicationFactor = 1; + + private int fetchSize = 1024 * 1024; + + private int minPartitionCount = 1; + + private int queueSize; public String getZkConnectionString() { return toConnectionString(this.zkNodes, this.defaultZkPort); @@ -131,23 +133,6 @@ class KafkaBinderConfigurationProperties { this.offsetUpdateShutdownTimeout = offsetUpdateShutdownTimeout; } - public KafkaMessageChannelBinder.StartOffset getStartOffset() { - return startOffset; - } - - public void setStartOffset(KafkaMessageChannelBinder.StartOffset startOffset) { - this.startOffset = startOffset; - } - - public boolean isResetOffsets() { - return resetOffsets; - } - - public void setResetOffsets(boolean resetOffsets) { - this.resetOffsets = resetOffsets; - } - - public int getZkSessionTimeout() { return this.zkSessionTimeout; } @@ -164,14 +149,6 @@ class KafkaBinderConfigurationProperties { this.zkConnectionTimeout = zkConnectionTimeout; } - public boolean isSyncProducer() { - return this.syncProducer; - } - - public void setSyncProducer(boolean syncProducer) { - this.syncProducer = syncProducer; - } - /** * Converts an array of host values to a comma-separated String. * @@ -189,4 +166,53 @@ class KafkaBinderConfigurationProperties { } return StringUtils.arrayToCommaDelimitedString(fullyFormattedHosts); } + + public int getMaxWait() { + return maxWait; + } + + public void setMaxWait(int maxWait) { + this.maxWait = maxWait; + } + + public int getRequiredAcks() { + return requiredAcks; + } + + public void setRequiredAcks(int requiredAcks) { + this.requiredAcks = requiredAcks; + } + + public int getReplicationFactor() { + return replicationFactor; + } + + public void setReplicationFactor(int replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public int getFetchSize() { + return fetchSize; + } + + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + public int getMinPartitionCount() { + return minPartitionCount; + } + + public void setMinPartitionCount(int minPartitionCount) { + this.minPartitionCount = minPartitionCount; + } + + public int getQueueSize() { + return queueSize; + } + + public void setQueueSize(int queueSize) { + this.queueSize = queueSize; + } + } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderDefaultProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderDefaultProperties.java deleted file mode 100644 index c70c5344f..000000000 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/config/KafkaBinderDefaultProperties.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.stream.binder.kafka.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author David Turanski - * @author Ilayaperumal Gopinathan - * @author Marius Bogoevici - */ -@ConfigurationProperties(value = "spring.cloud.stream.binder.kafka.default") -public class KafkaBinderDefaultProperties { - - private int batchSize; - - private long batchTimeout; - - private int requiredAcks; - - private int replicationFactor; - - private int concurrency; - - private String compressionCodec; - - private boolean autoCommitEnabled; - - private int fetchSize; - - private int minPartitionCount; - - private int queueSize; - - public int getBatchSize() { - return batchSize; - } - - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } - - public long getBatchTimeout() { - return batchTimeout; - } - - public void setBatchTimeout(long batchTimeout) { - this.batchTimeout = batchTimeout; - } - - public int getRequiredAcks() { - return requiredAcks; - } - - public void setRequiredAcks(int requiredAcks) { - this.requiredAcks = requiredAcks; - } - - public int getReplicationFactor() { - return replicationFactor; - } - - public void setReplicationFactor(int replicationFactor) { - this.replicationFactor = replicationFactor; - } - - public int getConcurrency() { - return concurrency; - } - - public void setConcurrency(int concurrency) { - this.concurrency = concurrency; - } - - public String getCompressionCodec() { - return compressionCodec; - } - - public void setCompressionCodec(String compressionCodec) { - this.compressionCodec = compressionCodec; - } - - public boolean isAutoCommitEnabled() { - return autoCommitEnabled; - } - - public void setAutoCommitEnabled(boolean autoCommitEnabled) { - this.autoCommitEnabled = autoCommitEnabled; - } - - public int getFetchSize() { - return fetchSize; - } - - public void setFetchSize(int fetchSize) { - this.fetchSize = fetchSize; - } - - public int getMinPartitionCount() { - return minPartitionCount; - } - - public void setMinPartitionCount(int minPartitionCount) { - this.minPartitionCount = minPartitionCount; - } - - public int getQueueSize() { - return queueSize; - } - - public void setQueueSize(int queueSize) { - this.queueSize = queueSize; - } -} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring-cloud-stream/kafka-binder.properties b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring-cloud-stream/kafka-binder.properties index e786029c1..780e42629 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring-cloud-stream/kafka-binder.properties +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring-cloud-stream/kafka-binder.properties @@ -8,14 +8,11 @@ spring.cloud.stream.binder.kafka.offsetUpdateCount=0 spring.cloud.stream.binder.kafka.offsetUpdateShutdownTimeout=2000 spring.cloud.stream.binder.kafka.zkSessionTimeout=10000 spring.cloud.stream.binder.kafka.zkConnectionTimeout=10000 -spring.cloud.stream.binder.kafka.syncProducer=false -spring.cloud.stream.binder.kafka.default.batchSize=16384 -spring.cloud.stream.binder.kafka.default.batchTimeout=0 -spring.cloud.stream.binder.kafka.default.requiredAcks=1 -spring.cloud.stream.binder.kafka.default.replicationFactor=1 -spring.cloud.stream.binder.kafka.default.concurrency=1 -spring.cloud.stream.binder.kafka.default.compressionCodec=none -spring.cloud.stream.binder.kafka.default.autoCommitEnabled=true -spring.cloud.stream.binder.kafka.default.fetchSize=1048576 -spring.cloud.stream.binder.kafka.default.minPartitionCount=1 -spring.cloud.stream.binder.kafka.default.queueSize=8192 +spring.cloud.stream.binder.kafka.batchSize=16384 +spring.cloud.stream.binder.kafka.batchTimeout=0 +spring.cloud.stream.binder.kafka.requiredAcks=1 +spring.cloud.stream.binder.kafka.replicationFactor=1 +spring.cloud.stream.binder.kafka.compressionCodec=none +spring.cloud.stream.binder.kafka.fetchSize=1048576 +spring.cloud.stream.binder.kafka.minPartitionCount=1 +spring.cloud.stream.binder.kafka.queueSize=8192 diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring.binders b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring.binders index 375468587..063a7400f 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring.binders +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/main/resources/META-INF/spring.binders @@ -1,2 +1,2 @@ kafka:\ -org.springframework.cloud.stream.binder.kafka.config.KafkaServiceAutoConfiguration +org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderTests.java index be57211eb..27bea088d 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderTests.java @@ -27,19 +27,17 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.Collection; -import java.util.Properties; import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; +import kafka.api.OffsetRequest; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; -import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.PartitionCapableBinderTests; import org.springframework.cloud.stream.binder.Spy; @@ -52,33 +50,27 @@ import org.springframework.integration.kafka.core.Partition; import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer; import org.springframework.integration.kafka.listener.MessageListener; import org.springframework.integration.kafka.support.ProducerConfiguration; +import org.springframework.integration.kafka.support.ProducerMetadata; import org.springframework.integration.kafka.support.ZookeeperConnect; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.support.GenericMessage; -import kafka.api.OffsetRequest; - /** * Integration tests for the {@link KafkaMessageChannelBinder}. - * * @author Eric Bottard * @author Marius Bogoevici * @author Mark Fisher * @author Ilayaperumal Gopinathan */ -public class KafkaBinderTests extends PartitionCapableBinderTests { +public class KafkaBinderTests extends PartitionCapableBinderTests { private final String CLASS_UNDER_TEST_NAME = KafkaMessageChannelBinder.class.getSimpleName(); - static { - System.setProperty("SCS_KAFKA_TEST_EMBEDDED", "true"); - } - @ClassRule - public static KafkaTestSupport kafkaTestSupport = new KafkaTestSupport(); + public static KafkaTestSupport kafkaTestSupport = new KafkaTestSupport(true); private KafkaTestBinder binder; @@ -88,13 +80,23 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { } @Override - protected Binder getBinder() { + protected KafkaTestBinder getBinder() { if (binder == null) { - binder = createKafkaTestBinder(); + binder = new KafkaTestBinder(kafkaTestSupport); } return binder; } + @Override + protected KafkaConsumerProperties createConsumerProperties() { + return new KafkaConsumerProperties(); + } + + @Override + protected KafkaProducerProperties createProducerProperties() { + return new KafkaProducerProperties(); + } + @Before public void init() { String multiplier = System.getenv("KAFKA_TIMEOUT_MULTIPLIER"); @@ -103,10 +105,6 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { } } - protected KafkaTestBinder createKafkaTestBinder() { - return new KafkaTestBinder(kafkaTestSupport, KafkaMessageChannelBinder.Mode.embeddedHeaders); - } - @Override protected boolean usesExplicitRouting() { return false; @@ -121,11 +119,11 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { public Spy spyOn(final String name) { KafkaMessageChannelBinder.validateTopicName(name); - KafkaTestBinder binderWrapper = (KafkaTestBinder) getBinder(); + KafkaTestBinder binderWrapper = getBinder(); // Rewind offset, as tests will have typically already sent the messages we're trying to consume KafkaMessageListenerContainer messageListenerContainer = binderWrapper.getCoreBinder().createMessageListenerContainer( - new Properties(), UUID.randomUUID().toString(), 1, name, OffsetRequest.EarliestTime()); + createConsumerProperties(), UUID.randomUUID().toString(), name, null, OffsetRequest.EarliestTime()); final BlockingQueue messages = new ArrayBlockingQueue(10); @@ -155,21 +153,22 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { @Test public void testCompression() throws Exception { - final String[] codecs = new String[] { null, "none", "gzip", "snappy" }; + final ProducerMetadata.CompressionType[] codecs = new ProducerMetadata.CompressionType[] { + ProducerMetadata.CompressionType.none, + ProducerMetadata.CompressionType.gzip, + ProducerMetadata.CompressionType.snappy }; byte[] ratherBigPayload = new byte[2048]; Arrays.fill(ratherBigPayload, (byte) 65); - Binder binder = getBinder(); + KafkaTestBinder binder = getBinder(); - for (String codec : codecs) { + for (ProducerMetadata.CompressionType codec : codecs) { DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Properties props = new Properties(); - if (codec != null) { - props.put(KafkaMessageChannelBinder.COMPRESSION_CODEC, codec); - } - Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, props); - Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null); + KafkaProducerProperties producerProperties = new KafkaProducerProperties(); + producerProperties.setCompressionType(codec); + Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, producerProperties); + Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, new KafkaConsumerProperties()); Message message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); @@ -187,14 +186,14 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { byte[] ratherBigPayload = new byte[2048]; Arrays.fill(ratherBigPayload, (byte) 65); - KafkaTestBinder binder = (KafkaTestBinder) getBinder(); + KafkaTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Properties producerProperties = new Properties(); - producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "10"); - Properties consumerProperties = new Properties(); - consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "10"); + KafkaProducerProperties producerProperties = new KafkaProducerProperties(); + producerProperties.setPartitionCount(10); + KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties(); + consumerProperties.setMinPartitionCount(10); long uniqueBindingId = System.currentTimeMillis(); Binding producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties); Binding consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties); @@ -212,86 +211,20 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { consumerBinding.unbind(); } - @Test - public void testCustomPartitionCountDoesNotOverrideModuleCountAndConcurrencyIfSmaller() throws Exception { - - byte[] ratherBigPayload = new byte[2048]; - Arrays.fill(ratherBigPayload, (byte) 65); - KafkaTestBinder binder = (KafkaTestBinder) getBinder(); - - - DirectChannel moduleOutputChannel = new DirectChannel(); - QueueChannel moduleInputChannel = new QueueChannel(); - Properties producerProps = new Properties(); - producerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5"); - producerProps.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "6"); - Properties consumerProps = new Properties(); - consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5"); - consumerProps.put(BinderPropertyKeys.CONCURRENCY, "6"); - long uniqueBindingId = System.currentTimeMillis(); - Binding producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps); - Binding consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProps); - Message message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build(); - // Let the consumer actually bind to the producer before sending a msg - binderBindUnbindLatency(); - moduleOutputChannel.send(message); - Message inbound = receive(moduleInputChannel); - assertNotNull(inbound); - assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload()); - Collection partitions = binder.getCoreBinder().getConnectionFactory().getPartitions( - "foo" + uniqueBindingId + ".0"); - assertThat(partitions, hasSize(6)); - producerBinding.unbind(); - consumerBinding.unbind(); - } - - @Test - public void testCustomPartitionCountOverridesModuleCountAndConcurrencyIfLarger() throws Exception { - - byte[] ratherBigPayload = new byte[2048]; - Arrays.fill(ratherBigPayload, (byte) 65); - KafkaTestBinder binder = (KafkaTestBinder) getBinder(); - - DirectChannel moduleOutputChannel = new DirectChannel(); - QueueChannel moduleInputChannel = new QueueChannel(); - Properties producerProps = new Properties(); - producerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "6"); - producerProps.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "5"); - Properties consumerProps = new Properties(); - consumerProps.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "6"); - consumerProps.put(BinderPropertyKeys.CONCURRENCY, "5"); - long uniqueBindingId = System.currentTimeMillis(); - Binding producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps); - Binding consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProps); - Message message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build(); - // Let the consumer actually bind to the producer before sending a msg - binderBindUnbindLatency(); - moduleOutputChannel.send(message); - Message inbound = receive(moduleInputChannel); - assertNotNull(inbound); - assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload()); - Collection partitions = binder.getCoreBinder().getConnectionFactory().getPartitions( - "foo" + uniqueBindingId + ".0"); - assertThat(partitions, hasSize(6)); - producerBinding.unbind(); - consumerBinding.unbind(); - } - @Test public void testCustomPartitionCountDoesNotOverridePartitioningIfSmaller() throws Exception { byte[] ratherBigPayload = new byte[2048]; Arrays.fill(ratherBigPayload, (byte) 65); - KafkaTestBinder binder = (KafkaTestBinder) getBinder(); + KafkaTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Properties producerProperties = new Properties(); - producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "3"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "5"); - producerProperties.put(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, "payload"); - Properties consumerProperties = new Properties(); - consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "3"); + KafkaProducerProperties producerProperties = new KafkaProducerProperties(); + producerProperties.setPartitionCount(5); + producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload")); + KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties(); + consumerProperties.setMinPartitionCount(3); long uniqueBindingId = System.currentTimeMillis(); Binding producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties); Binding consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties); @@ -314,16 +247,15 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { byte[] ratherBigPayload = new byte[2048]; Arrays.fill(ratherBigPayload, (byte) 65); - KafkaTestBinder binder = (KafkaTestBinder) getBinder(); + KafkaTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Properties producerProperties = new Properties(); - producerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3"); - producerProperties.put(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, "payload"); - Properties consumerProperties = new Properties(); - consumerProperties.put(BinderPropertyKeys.MIN_PARTITION_COUNT, "5"); + KafkaProducerProperties producerProperties = new KafkaProducerProperties(); + producerProperties.setPartitionCount(5); + producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload")); + KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties(); + consumerProperties.setMinPartitionCount(5); long uniqueBindingId = System.currentTimeMillis(); Binding producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties); Binding consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null, moduleInputChannel, consumerProperties); @@ -351,14 +283,13 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { binder.setApplicationContext(context); binder.afterPropertiesSet(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); QueueChannel input1 = new QueueChannel(); String testTopicName = UUID.randomUUID().toString(); - binder.bindProducer(testTopicName,output,properties); + binder.bindProducer(testTopicName, output, new KafkaProducerProperties()); String testPayload1 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload1.getBytes())); - binder.bindConsumer(testTopicName, "startOffsets", input1, properties); + binder.bindConsumer(testTopicName, "startOffsets", input1, new KafkaConsumerProperties()); Message receivedMessage1 = (Message) receive(input1); assertThat(receivedMessage1, not(nullValue())); assertThat(new String(receivedMessage1.getPayload()), equalTo(testPayload1)); @@ -372,21 +303,16 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { @Test @SuppressWarnings("unchecked") public void testEarliest() throws Exception { - KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()), - kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString()); - GenericApplicationContext context = new GenericApplicationContext(); - context.refresh(); - binder.setApplicationContext(context); - binder.afterPropertiesSet(); - binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); + KafkaTestBinder binder = getBinder(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); QueueChannel input1 = new QueueChannel(); String testTopicName = UUID.randomUUID().toString(); - binder.bindProducer(testTopicName,output,properties); + binder.bindProducer(testTopicName, output, new KafkaProducerProperties()); String testPayload1 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload1.getBytes())); + KafkaConsumerProperties properties = new KafkaConsumerProperties(); + properties.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); binder.bindConsumer(testTopicName, "startOffsets", input1, properties); Message receivedMessage1 = (Message) receive(input1); assertThat(receivedMessage1, not(nullValue())); @@ -400,23 +326,18 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { @Test @SuppressWarnings("unchecked") public void testReset() throws Exception { - KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()), - kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString()); - GenericApplicationContext context = new GenericApplicationContext(); - context.refresh(); - binder.setApplicationContext(context); - binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); - binder.setResetOffsets(true); - binder.afterPropertiesSet(); + KafkaTestBinder binder = getBinder(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); QueueChannel input1 = new QueueChannel(); String testTopicName = UUID.randomUUID().toString(); - Binding producerBinding = binder.bindProducer(testTopicName, output, properties); + Binding producerBinding = binder.bindProducer(testTopicName, output, new KafkaProducerProperties()); String testPayload1 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload1.getBytes())); + KafkaConsumerProperties properties = new KafkaConsumerProperties(); + properties.setResetOffsets(true); + properties.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); Binding consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, properties); Message receivedMessage1 = (Message) receive(input1); @@ -431,8 +352,11 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { String testPayload3 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload3.getBytes())); + KafkaConsumerProperties properties2 = new KafkaConsumerProperties(); + properties2.setResetOffsets(true); + properties2.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); consumerBinding = - binder.bindConsumer(testTopicName, "startOffsets", input1, properties); + binder.bindConsumer(testTopicName, "startOffsets", input1, properties2); Message receivedMessage4 = (Message) receive(input1); assertThat(receivedMessage4, not(nullValue())); assertThat(new String(receivedMessage4.getPayload()), equalTo(testPayload1)); @@ -455,16 +379,15 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { context.refresh(); binder.setApplicationContext(context); binder.afterPropertiesSet(); - binder.setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); QueueChannel input1 = new QueueChannel(); String testTopicName = UUID.randomUUID().toString(); - Binding producerBinding = binder.bindProducer(testTopicName, output, properties); + Binding producerBinding = binder.bindProducer(testTopicName, output, new KafkaProducerProperties()); String testPayload1 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload1.getBytes())); - Binding consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, properties); + KafkaConsumerProperties firstConsumerProperties = new KafkaConsumerProperties(); + Binding consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, firstConsumerProperties); Message receivedMessage1 = (Message) receive(input1); assertThat(receivedMessage1, not(nullValue())); String testPayload2 = "foo-" + UUID.randomUUID().toString(); @@ -478,7 +401,7 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { output.send(new GenericMessage<>(testPayload3.getBytes())); consumerBinding = - binder.bindConsumer(testTopicName, "startOffsets", input1, properties); + binder.bindConsumer(testTopicName, "startOffsets", input1, new KafkaConsumerProperties()); Message receivedMessage3 = (Message) receive(input1); assertThat(receivedMessage3, not(nullValue())); assertThat(new String(receivedMessage3.getPayload()), equalTo(testPayload3)); @@ -490,7 +413,6 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { public void testSyncProducerMetadata() throws Exception { KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(new ZookeeperConnect(kafkaTestSupport.getZkConnectString()), kafkaTestSupport.getBrokerAddress(), kafkaTestSupport.getZkConnectString()); - binder.setSyncProducer(true); GenericApplicationContext context = new GenericApplicationContext(); context.refresh(); binder.setApplicationContext(context); @@ -499,7 +421,9 @@ public class KafkaBinderTests extends PartitionCapableBinderTests { DirectChannel output = new DirectChannel(); String testTopicName = UUID.randomUUID().toString(); - Binding producerBinding = binder.bindProducer(testTopicName, output, null); + KafkaProducerProperties properties = new KafkaProducerProperties(); + properties.setSync(true); + Binding producerBinding = binder.bindProducer(testTopicName, output, properties); DirectFieldAccessor accessor = new DirectFieldAccessor(extractEndpoint(producerBinding)); MessageHandler handler = (MessageHandler) accessor.getPropertyValue("handler"); DirectFieldAccessor accessor1 = new DirectFieldAccessor(handler); diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaTestBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaTestBinder.java index e4209c5b9..49563d988 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaTestBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaTestBinder.java @@ -43,15 +43,9 @@ import com.esotericsoftware.kryo.Registration; * @author Gary Russell * @author Soby Chacko */ -public class KafkaTestBinder extends AbstractTestBinder { +public class KafkaTestBinder extends AbstractTestBinder { public KafkaTestBinder(KafkaTestSupport kafkaTestSupport) { - this(kafkaTestSupport, KafkaMessageChannelBinder.Mode.embeddedHeaders); - } - - - public KafkaTestBinder(KafkaTestSupport kafkaTestSupport, - KafkaMessageChannelBinder.Mode mode) { try { ZookeeperConnect zookeeperConnect = new ZookeeperConnect(); @@ -60,8 +54,6 @@ public class KafkaTestBinder extends AbstractTestBinder binder = getBinder(); - Properties properties = new Properties(); - properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport"); - properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.kafka.RawKafkaPartitionTestSupport"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3"); - properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2"); + KafkaTestBinder binder = getBinder(); + KafkaProducerProperties properties = new KafkaProducerProperties(); + properties.setPartitionKeyExtractorClass(RawKafkaPartitionTestSupport.class); + properties.setPartitionSelectorClass(RawKafkaPartitionTestSupport.class); + properties.setPartitionCount(3); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); Binding outputBinding = binder.bindProducer("partJ.0", output, properties); - properties.clear(); - properties.put("concurrency", "2"); - properties.put("count","3"); - properties.put("partitionIndex", "0"); + + KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties(); + consumerProperties.setConcurrency(2); + consumerProperties.setInstanceCount(3); + consumerProperties.setInstanceIndex(0); + consumerProperties.setPartitioned(true); QueueChannel input0 = new QueueChannel(); input0.setBeanName("test.input0J"); - Binding input0Binding = binder.bindConsumer("partJ.0", "test", input0, properties); - properties.put("partitionIndex", "1"); + Binding input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties); + consumerProperties.setInstanceIndex(1); QueueChannel input1 = new QueueChannel(); input1.setBeanName("test.input1J"); - Binding input1Binding = binder.bindConsumer("partJ.0", "test", input1, properties); - properties.put("partitionIndex", "2"); + Binding input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties); + consumerProperties.setInstanceIndex(2); QueueChannel input2 = new QueueChannel(); input2.setBeanName("test.input2J"); - Binding input2Binding = binder.bindConsumer("partJ.0", "test", input2, properties); + Binding input2Binding = binder.bindConsumer("partJ.0", "test", input2, consumerProperties); output.send(new GenericMessage<>(new byte[]{(byte)0})); output.send(new GenericMessage<>(new byte[]{(byte)1})); @@ -111,12 +104,11 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests { @Test @Override public void testPartitionedModuleSpEL() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("partitionKeyExpression", "payload[0]"); - properties.put("partitionSelectorExpression", "hashCode()"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3"); - properties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2"); + KafkaTestBinder binder = getBinder(); + KafkaProducerProperties properties = new KafkaProducerProperties(); + properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload[0]")); + properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()")); + properties.setPartitionCount(3); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); @@ -129,21 +121,22 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests { } - properties.clear(); - properties.put("concurrency", "2"); - properties.put("partitionIndex", "0"); - properties.put("count","3"); + KafkaConsumerProperties consumerProperties = new KafkaConsumerProperties(); + consumerProperties.setConcurrency(2); + consumerProperties.setInstanceIndex(0); + consumerProperties.setInstanceCount(3); + consumerProperties.setPartitioned(true); QueueChannel input0 = new QueueChannel(); input0.setBeanName("test.input0S"); - Binding input0Binding = binder.bindConsumer("part.0", "test", input0, properties); - properties.put("partitionIndex", "1"); + Binding input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties); + consumerProperties.setInstanceIndex(1); QueueChannel input1 = new QueueChannel(); input1.setBeanName("test.input1S"); - Binding input1Binding = binder.bindConsumer("part.0", "test", input1, properties); - properties.put("partitionIndex", "2"); + Binding input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties); + consumerProperties.setInstanceIndex(2); QueueChannel input2 = new QueueChannel(); input2.setBeanName("test.input2S"); - Binding input2Binding = binder.bindConsumer("part.0", "test", input2, properties); + Binding input2Binding = binder.bindConsumer("part.0", "test", input2, consumerProperties); Message message2 = MessageBuilder.withPayload(new byte[]{2}) .setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo") @@ -177,11 +170,11 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests { @Test @Override public void testSendAndReceive() throws Exception { - Binder binder = getBinder(); + KafkaTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null); - Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null); + Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, new KafkaProducerProperties()); + Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, new KafkaConsumerProperties()); Message message = MessageBuilder.withPayload("foo".getBytes()).build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); @@ -203,20 +196,20 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests { @Test public void testSendAndReceiveWithExplicitConsumerGroup() { - Binder binder = getBinder(); + KafkaTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); // Test pub/sub by emulating how StreamPlugin handles taps QueueChannel module1InputChannel = new QueueChannel(); QueueChannel module2InputChannel = new QueueChannel(); QueueChannel module3InputChannel = new QueueChannel(); - Binding producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, null); - Binding input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel, null); + Binding producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, new KafkaProducerProperties()); + Binding input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel, new KafkaConsumerProperties()); // A new module is using the tap as an input channel String fooTapName = "baz.0"; - Binding input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel, null); + Binding input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel, new KafkaConsumerProperties()); // Another new module is using tap as an input channel String barTapName = "baz.0"; - Binding input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null); + Binding input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, new KafkaConsumerProperties()); Message message = MessageBuilder.withPayload("foo".getBytes()).build(); boolean success = false; @@ -252,7 +245,7 @@ public class RawModeKafkaBinderTests extends KafkaBinderTests { assertNull(receive(module3InputChannel)); // re-subscribed tap does receive the message - input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, null); + input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, new KafkaConsumerProperties()); assertNotNull(receive(module3InputChannel)); // clean up diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java new file mode 100644 index 000000000..62b72b097 --- /dev/null +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java @@ -0,0 +1,148 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder.rabbit; + +import org.springframework.amqp.core.AcknowledgeMode; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.util.Assert; + +/** + * @author Marius Bogoevici + */ +public class RabbitConsumerProperties extends ConsumerProperties { + + private String prefix = ""; + + private boolean transacted = false; + + private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; + + private int maxConcurrency = 1; + + private int prefetch = 1; + + private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"}; + + private int txSize = 1; + + private boolean autoBindDlq = false; + + private boolean durableSubscription = true; + + private boolean republishToDlq = false; + + private boolean requeueRejected = true; + + private String replyHeaderPatterns; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public boolean isTransacted() { + return transacted; + } + + public void setTransacted(boolean transacted) { + this.transacted = transacted; + } + + public AcknowledgeMode getAcknowledgeMode() { + return acknowledgeMode; + } + + public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { + Assert.notNull("Acknowledge mode cannot be null"); + this.acknowledgeMode = acknowledgeMode; + } + + public int getMaxConcurrency() { + return maxConcurrency; + } + + public void setMaxConcurrency(int maxConcurrency) { + this.maxConcurrency = maxConcurrency; + } + + public int getPrefetch() { + return prefetch; + } + + public void setPrefetch(int prefetch) { + this.prefetch = prefetch; + } + + public String[] getRequestHeaderPatterns() { + return requestHeaderPatterns; + } + + public void setRequestHeaderPatterns(String[] requestHeaderPatterns) { + this.requestHeaderPatterns = requestHeaderPatterns; + } + + public int getTxSize() { + return txSize; + } + + public void setTxSize(int txSize) { + this.txSize = txSize; + } + + public boolean isAutoBindDlq() { + return autoBindDlq; + } + + public void setAutoBindDlq(boolean autoBindDlq) { + this.autoBindDlq = autoBindDlq; + } + + public boolean isDurableSubscription() { + return durableSubscription; + } + + public void setDurableSubscription(boolean durableSubscription) { + this.durableSubscription = durableSubscription; + } + + public boolean isRepublishToDlq() { + return republishToDlq; + } + + public void setRepublishToDlq(boolean republishToDlq) { + this.republishToDlq = republishToDlq; + } + + public boolean isRequeueRejected() { + return requeueRejected; + } + + public void setRequeueRejected(boolean requeueRejected) { + this.requeueRejected = requeueRejected; + } + + public String getReplyHeaderPatterns() { + return replyHeaderPatterns; + } + + public void setReplyHeaderPatterns(String replyHeaderPatterns) { + this.replyHeaderPatterns = replyHeaderPatterns; + } +} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java index 25c000310..cf1e4d0f1 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java @@ -19,25 +19,23 @@ package org.springframework.cloud.stream.binder.rabbit; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; -import java.util.Properties; -import java.util.Set; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Envelope; import org.aopalliance.aop.Advice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.AmqpConnectException; import org.springframework.amqp.UncategorizedAmqpException; -import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.AnonymousQueue; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Exchange; -import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.core.Queue; @@ -62,17 +60,14 @@ import org.springframework.amqp.support.postprocessor.GZipPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.cloud.stream.binder.AbstractBinder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.DefaultBinding; -import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor; import org.springframework.cloud.stream.binder.MessageValues; import org.springframework.cloud.stream.binder.PartitionHandler; import org.springframework.context.Lifecycle; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter; @@ -94,10 +89,6 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Envelope; - /** * A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ. * @@ -109,86 +100,13 @@ import com.rabbitmq.client.Envelope; * @author David Turanski * @author Marius Bogoevici */ -public class RabbitMessageChannelBinder extends AbstractBinder { +public class RabbitMessageChannelBinder extends AbstractBinder { public static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR = new AnonymousQueue.Base64UrlNamingStrategy("anonymous."); - private static final AcknowledgeMode DEFAULT_ACKNOWLEDGE_MODE = AcknowledgeMode.AUTO; - - private static final MessageDeliveryMode DEFAULT_DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT; - - private static final boolean DEFAULT_DEFAULT_REQUEUE_REJECTED = true; - - private static final int DEFAULT_MAX_CONCURRENCY = 1; - - private static final int DEFAULT_PREFETCH_COUNT = 1; - - static final String DEFAULT_RABBIT_PREFIX = "binder."; - - private static final int DEFAULT_TX_SIZE = 1; - - private static final String[] DEFAULT_REQUEST_HEADER_PATTERNS = new String[] { "STANDARD_REQUEST_HEADERS", "*" }; - - private static final String[] DEFAULT_REPLY_HEADER_PATTERNS = new String[] { "STANDARD_REPLY_HEADERS", "*" }; - private static final String DEAD_LETTER_EXCHANGE = "DLX"; - private static final Set RABBIT_CONSUMER_PROPERTIES = new HashSet(Arrays.asList(new String[] { - BinderPropertyKeys.MAX_CONCURRENCY, - RabbitPropertiesAccessor.ACK_MODE, - RabbitPropertiesAccessor.PREFETCH, - RabbitPropertiesAccessor.PREFIX, - RabbitPropertiesAccessor.REQUEST_HEADER_PATTERNS, - RabbitPropertiesAccessor.REQUEUE, - RabbitPropertiesAccessor.TRANSACTED, - RabbitPropertiesAccessor.TX_SIZE, - RabbitPropertiesAccessor.AUTO_BIND_DLQ, - RabbitPropertiesAccessor.REPUBLISH_TO_DLQ, - RabbitPropertiesAccessor.DURABLE - })); - - /** - * Standard + retry + rabbit consumer properties. - */ - private static final Set SUPPORTED_BASIC_CONSUMER_PROPERTIES = new SetBuilder() - .addAll(CONSUMER_STANDARD_PROPERTIES) - .addAll(CONSUMER_RETRY_PROPERTIES) - .addAll(RABBIT_CONSUMER_PROPERTIES) - .build(); - - /** - * Basic + durable + concurrency + partitioning. - */ - private static final Set SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder() - .addAll(SUPPORTED_BASIC_CONSUMER_PROPERTIES) - .add(BinderPropertyKeys.CONCURRENCY) - .add(BinderPropertyKeys.PARTITION_INDEX) - .build(); - - /** - * Rabbit producer properties. - */ - private static final Set SUPPORTED_BASIC_PRODUCER_PROPERTIES = new SetBuilder() - .addAll(PRODUCER_STANDARD_PROPERTIES) - .add(RabbitPropertiesAccessor.DELIVERY_MODE) - .add(RabbitPropertiesAccessor.PREFIX) - .add(RabbitPropertiesAccessor.REQUEST_HEADER_PATTERNS) - .add(BinderPropertyKeys.COMPRESS) - .add(BinderPropertyKeys.REQUIRED_GROUPS) - .build(); - - /** - * Partitioning + rabbit producer properties. - */ - private static final Set SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder() - .addAll(PRODUCER_PARTITIONING_PROPERTIES) - .addAll(SUPPORTED_BASIC_PRODUCER_PROPERTIES) - .addAll(PRODUCER_BATCHING_BASIC_PROPERTIES) - .addAll(PRODUCER_BATCHING_ADVANCED_PROPERTIES) - .add(RabbitPropertiesAccessor.AUTO_BIND_DLQ) - .build(); - private static final MessagePropertiesConverter inboundMessagePropertiesConverter = new DefaultMessagePropertiesConverter() { @@ -217,34 +135,6 @@ public class RabbitMessageChannelBinder extends AbstractBinder { private MessagePostProcessor compressingPostProcessor = new GZipPostProcessor(); - // Default RabbitMQ Container properties - - private volatile AcknowledgeMode defaultAcknowledgeMode = DEFAULT_ACKNOWLEDGE_MODE; - - private volatile boolean defaultChannelTransacted; - - private volatile MessageDeliveryMode defaultDefaultDeliveryMode = DEFAULT_DEFAULT_DELIVERY_MODE; - - private volatile boolean defaultDefaultRequeueRejected = DEFAULT_DEFAULT_REQUEUE_REJECTED; - - private volatile int defaultMaxConcurrency = DEFAULT_MAX_CONCURRENCY; - - private volatile int defaultPrefetchCount = DEFAULT_PREFETCH_COUNT; - - private volatile int defaultTxSize = DEFAULT_TX_SIZE; - - protected volatile boolean defaultDurableSubscription = true; - - private volatile String defaultPrefix = DEFAULT_RABBIT_PREFIX; - - private volatile String[] defaultRequestHeaderPatterns = DEFAULT_REQUEST_HEADER_PATTERNS; - - private volatile String[] defaultReplyHeaderPatterns = DEFAULT_REPLY_HEADER_PATTERNS; - - private volatile boolean defaultAutoBindDLQ = false; - - private volatile boolean defaultRepublishToDLQ = false; - private volatile String[] addresses; private volatile String[] adminAddresses; @@ -293,71 +183,6 @@ public class RabbitMessageChannelBinder extends AbstractBinder { this.compressingPostProcessor = compressingPostProcessor; } - public void setDefaultAcknowledgeMode(AcknowledgeMode defaultAcknowledgeMode) { - Assert.notNull(defaultAcknowledgeMode, "'defaultAcknowledgeMode' cannot be null"); - this.defaultAcknowledgeMode = defaultAcknowledgeMode; - } - - public void setDefaultChannelTransacted(boolean defaultChannelTransacted) { - this.defaultChannelTransacted = defaultChannelTransacted; - } - - public void setDefaultDefaultDeliveryMode(MessageDeliveryMode defaultDefaultDeliveryMode) { - Assert.notNull(defaultDefaultDeliveryMode, "'defaultDeliveryMode' cannot be null"); - this.defaultDefaultDeliveryMode = defaultDefaultDeliveryMode; - } - - public void setDefaultDefaultRequeueRejected(boolean defaultDefaultRequeueRejected) { - this.defaultDefaultRequeueRejected = defaultDefaultRequeueRejected; - } - - /** - * Set the binder's default max consumers; can be overridden by consumer.maxConcurrency. Values less than 'concurrency' - * will be coerced to be equal to concurrency. - * @param defaultMaxConcurrency The default max concurrency. - */ - public void setDefaultMaxConcurrency(int defaultMaxConcurrency) { - this.defaultMaxConcurrency = defaultMaxConcurrency; - } - - public void setDefaultPrefetchCount(int defaultPrefetchCount) { - this.defaultPrefetchCount = defaultPrefetchCount; - } - - public void setDefaultTxSize(int defaultTxSize) { - this.defaultTxSize = defaultTxSize; - } - - /** - * Set whether subscriptions are durable. - * @param defaultDurableSubscription true for durable (default false). - */ - public void setDefaultDurableSubscription(boolean defaultDurableSubscription) { - this.defaultDurableSubscription = defaultDurableSubscription; - } - - - public void setDefaultPrefix(String defaultPrefix) { - Assert.notNull(defaultPrefix, "'defaultPrefix' cannot be null"); - this.defaultPrefix = defaultPrefix.trim(); - } - - public void setDefaultRequestHeaderPatterns(String[] defaultRequestHeaderPatterns) { - this.defaultRequestHeaderPatterns = Arrays.copyOf(defaultRequestHeaderPatterns, - defaultRequestHeaderPatterns.length); - } - - public void setDefaultReplyHeaderPatterns(String[] defaultReplyHeaderPatterns) { - this.defaultReplyHeaderPatterns = Arrays.copyOf(defaultReplyHeaderPatterns, defaultReplyHeaderPatterns.length); - } - - public void setDefaultAutoBindDLQ(boolean defaultAutoBindDLQ) { - this.defaultAutoBindDLQ = defaultAutoBindDLQ; - } - - public void setDefaultRepublishToDLQ(boolean defaultRepublishToDLQ) { - this.defaultRepublishToDLQ = defaultRepublishToDLQ; - } public void setAddresses(String[] addresses) { this.addresses = Arrays.copyOf(addresses, addresses.length); @@ -405,23 +230,21 @@ public class RabbitMessageChannelBinder extends AbstractBinder { } @Override - public Binding doBindConsumer(String name, String group, MessageChannel inputChannel, Properties properties) { + public Binding doBindConsumer(String name, String group, MessageChannel inputChannel, RabbitConsumerProperties properties) { boolean anonymousConsumer = !StringUtils.hasText(group); String baseQueueName = anonymousConsumer ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName()) : groupedName(name, group); if (this.logger.isInfoEnabled()) { this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name); } - validateConsumerProperties(baseQueueName, properties, SUPPORTED_CONSUMER_PROPERTIES); - RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties); - String prefix = accessor.getPrefix(this.defaultPrefix); + String prefix = properties.getPrefix(); String exchangeName = applyPrefix(prefix, name); TopicExchange exchange = new TopicExchange(exchangeName); declareExchange(exchangeName, exchange); String queueName = applyPrefix(prefix, baseQueueName); - boolean partitioned = !anonymousConsumer && accessor.getPartitionIndex() >= 0; - boolean durable = !anonymousConsumer && accessor.isDurable(this.defaultDurableSubscription); + boolean partitioned = !anonymousConsumer && properties.isPartitioned(); + boolean durable = !anonymousConsumer && properties.isDurableSubscription(); Queue queue; if (anonymousConsumer) { @@ -429,11 +252,11 @@ public class RabbitMessageChannelBinder extends AbstractBinder { } else { if (partitioned) { - String partitionSuffix = "-" + accessor.getPartitionIndex(); + String partitionSuffix = "-" + properties.getInstanceIndex(); queueName += partitionSuffix; } if (durable) { - queue = new Queue(queueName, true, false, false, queueArgs(accessor, queueName)); + queue = new Queue(queueName, true, false, false, queueArgs(queueName, properties.getPrefix(), properties.isAutoBindDlq())); } else { queue = new Queue(queueName, false, false, true); @@ -443,31 +266,30 @@ public class RabbitMessageChannelBinder extends AbstractBinder { declareQueue(queueName, queue); if (partitioned) { - String bindingKey = String.format("%s-%d", name, accessor.getPartitionIndex()); + String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex()); declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey)); } else { declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#")); } - Binding binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, accessor); + Binding binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties); if (durable) { - autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, accessor); + autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, properties.getPrefix(), properties.isAutoBindDlq()); } return binding; - } - private Map queueArgs(RabbitPropertiesAccessor accessor, String queueName) { + private Map queueArgs(String queueName, String prefix, boolean bindDlq) { Map args = new HashMap<>(); - if (accessor.getAutoBindDLQ(this.defaultAutoBindDLQ)) { - args.put("x-dead-letter-exchange", applyPrefix(accessor.getPrefix(this.defaultPrefix), "DLX")); + if (bindDlq) { + args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX")); args.put("x-dead-letter-routing-key", queueName); } return args; } private Binding doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue, - final RabbitPropertiesAccessor properties) { + final RabbitConsumerProperties properties) { DefaultBinding consumerBinding = null; // TODO https://github.com/spring-cloud/spring-cloud-stream/issues/401 ClassLoader originalClassloader = Thread.currentThread().getContextClassLoader(); @@ -475,31 +297,30 @@ public class RabbitMessageChannelBinder extends AbstractBinder { ClassUtils.overrideThreadContextClassLoader(SimpleMessageListenerContainer.class.getClassLoader()); SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer( this.connectionFactory); - listenerContainer.setAcknowledgeMode(properties.getAcknowledgeMode(this.defaultAcknowledgeMode)); - listenerContainer.setChannelTransacted(properties.getTransacted(this.defaultChannelTransacted)); - listenerContainer.setDefaultRequeueRejected(properties.getRequeueRejected(this - .defaultDefaultRequeueRejected)); + listenerContainer.setAcknowledgeMode(properties.getAcknowledgeMode()); + listenerContainer.setChannelTransacted(properties.isTransacted()); + listenerContainer.setDefaultRequeueRejected(properties.isRequeueRejected()); - int concurrency = properties.getConcurrency(this.defaultConcurrency); + int concurrency = properties.getConcurrency(); concurrency = concurrency > 0 ? concurrency : 1; listenerContainer.setConcurrentConsumers(concurrency); - int maxConcurrency = properties.getMaxConcurrency(this.defaultMaxConcurrency); + int maxConcurrency = properties.getMaxConcurrency(); if (maxConcurrency > concurrency) { listenerContainer.setMaxConcurrentConsumers(maxConcurrency); } - listenerContainer.setPrefetchCount(properties.getPrefetchCount(this.defaultPrefetchCount)); - listenerContainer.setTxSize(properties.getTxSize(this.defaultTxSize)); + listenerContainer.setPrefetchCount(properties.getPrefetch()); + listenerContainer.setTxSize(properties.getTxSize()); listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-")); listenerContainer.setQueues(queue); - int maxAttempts = properties.getMaxAttempts(this.defaultMaxAttempts); - if (maxAttempts > 1 || properties.getRepublishToDLQ(this.defaultRepublishToDLQ)) { + int maxAttempts = properties.getMaxAttempts(); + if (maxAttempts > 1 || properties.isRepublishToDlq()) { RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless() .maxAttempts(maxAttempts) - .backOffOptions(properties.getBackOffInitialInterval(this.defaultBackOffInitialInterval), - properties.getBackOffMultiplier(this.defaultBackOffMultiplier), - properties.getBackOffMaxInterval(this.defaultBackOffMaxInterval)) - .recoverer(determineRecoverer(name, properties)) + .backOffOptions(properties.getBackOffInitialInterval(), + properties.getBackOffMultiplier(), + properties.getBackOffMaxInterval()) + .recoverer(determineRecoverer(name, properties.getPrefix(), properties.isRepublishToDlq())) .build(); listenerContainer.setAdviceChain(new Advice[] { retryInterceptor }); } @@ -514,15 +335,14 @@ public class RabbitMessageChannelBinder extends AbstractBinder { adapter.setOutputChannel(bridgeToModuleChannel); adapter.setBeanName("inbound." + name); DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper(); - mapper.setRequestHeaderNames(properties.getRequestHeaderPattens(this.defaultRequestHeaderPatterns)); - mapper.setReplyHeaderNames(properties.getReplyHeaderPattens(this.defaultReplyHeaderPatterns)); + mapper.setRequestHeaderNames(properties.getRequestHeaderPatterns()); + mapper.setReplyHeaderNames(properties.getReplyHeaderPatterns()); adapter.setHeaderMapper(mapper); adapter.afterPropertiesSet(); - consumerBinding = new DefaultBinding(name, group, moduleInputChannel, adapter, properties) { - + consumerBinding = new DefaultBinding(name, group, moduleInputChannel, adapter) { @Override protected void afterUnbind() { - cleanAutoDeclareContext(properties.getPrefix(defaultPrefix), name); + cleanAutoDeclareContext(properties.getPrefix(), name); } }; ReceivingHandler convertingBridge = new ReceivingHandler(); @@ -538,10 +358,9 @@ public class RabbitMessageChannelBinder extends AbstractBinder { return consumerBinding; } - private MessageRecoverer determineRecoverer(String name, RabbitPropertiesAccessor properties) { - if (properties.getRepublishToDLQ(this.defaultRepublishToDLQ)) { + private MessageRecoverer determineRecoverer(String name, String prefix, boolean republish) { + if (republish) { RabbitTemplate errorTemplate = new RabbitTemplate(this.connectionFactory); - String prefix = properties.getPrefix(this.defaultPrefix); RepublishMessageRecoverer republishMessageRecoverer = new RepublishMessageRecoverer(errorTemplate, deadLetterExchangeName(prefix), applyPrefix(prefix, name)); @@ -552,40 +371,38 @@ public class RabbitMessageChannelBinder extends AbstractBinder { } } - private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitPropertiesAccessor properties, - RabbitTemplate rabbitTemplate) { - String prefix = properties.getPrefix(this.defaultPrefix); + private AmqpOutboundEndpoint buildOutboundEndpoint(final String name, RabbitProducerProperties properties, + RabbitTemplate rabbitTemplate) { + String prefix = properties.getPrefix(); String exchangeName = applyPrefix(prefix, name); - String partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass(); - Expression partitionKeyExpression = properties.getPartitionKeyExpression(); TopicExchange exchange = new TopicExchange(exchangeName); declareExchange(exchangeName, exchange); AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate); endpoint.setExchangeName(exchange.getName()); - if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) { + if (!properties.isPartitioned()) { endpoint.setRoutingKey(name); } else { endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name))); } - for (String requiredGroupName : properties.getRequiredGroups(defaultRequiredGroups)) { + for (String requiredGroupName : properties.getRequiredGroups()) { String baseQueueName = exchangeName + "." + requiredGroupName; - if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) { - Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(properties, baseQueueName)); + if (!properties.isPartitioned()) { + Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(baseQueueName, prefix, properties.isAutoBindDlq())); declareQueue(baseQueueName, queue); - autoBindDLQ(baseQueueName, baseQueueName, properties); + autoBindDLQ(baseQueueName, baseQueueName, properties.getPrefix(), properties.isAutoBindDlq()); org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(name); declareBinding(baseQueueName, binding); } else { // if the stream is partitioned, create one queue for each target partition for the default group - for (int i = 0; i < properties.getNextModuleCount(); i++) { + for (int i = 0; i < properties.getPartitionCount(); i++) { String partitionSuffix = "-" + i; String partitionQueueName = baseQueueName + partitionSuffix; Queue queue = new Queue(partitionQueueName, true, false, false, - queueArgs(properties, partitionQueueName)); + queueArgs(partitionQueueName, properties.getPrefix(), properties.isAutoBindDlq())); declareQueue(queue.getName(), queue); - autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties); + autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties.getPrefix(), properties.isAutoBindDlq()); declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(name + partitionSuffix)); } } @@ -594,40 +411,38 @@ public class RabbitMessageChannelBinder extends AbstractBinder { return endpoint; } - private void configureOutboundHandler(AmqpOutboundEndpoint handler, RabbitPropertiesAccessor properties) { + private void configureOutboundHandler(AmqpOutboundEndpoint handler, RabbitProducerProperties producerProperties) { DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper(); - mapper.setRequestHeaderNames(properties.getRequestHeaderPattens(this.defaultRequestHeaderPatterns)); - mapper.setReplyHeaderNames(properties.getReplyHeaderPattens(this.defaultReplyHeaderPatterns)); + mapper.setRequestHeaderNames(producerProperties.getRequestHeaderPatterns()); + mapper.setReplyHeaderNames(producerProperties.getReplyHeaderPatterns()); handler.setHeaderMapper(mapper); - handler.setDefaultDeliveryMode(properties.getDeliveryMode(this.defaultDefaultDeliveryMode)); + handler.setDefaultDeliveryMode(producerProperties.getDeliveryMode()); handler.setBeanFactory(this.getBeanFactory()); handler.afterPropertiesSet(); } @Override - public Binding bindProducer(String name, MessageChannel outputChannel, Properties properties) { - validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES); - RabbitPropertiesAccessor accessor = new RabbitPropertiesAccessor(properties); - String exchangeName = applyPrefix(accessor.getPrefix(this.defaultPrefix), name); + public Binding doBindProducer(String name, MessageChannel outputChannel, RabbitProducerProperties producerProperties) { + String exchangeName = applyPrefix(producerProperties.getPrefix(), name); TopicExchange exchange = new TopicExchange(exchangeName); declareExchange(exchangeName, exchange); - AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, accessor, determineRabbitTemplate(accessor)); - return doRegisterProducer(name, outputChannel, endpoint, accessor); + AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, producerProperties, determineRabbitTemplate(producerProperties)); + return doRegisterProducer(name, outputChannel, endpoint, producerProperties); } - private RabbitTemplate determineRabbitTemplate(RabbitPropertiesAccessor properties) { + private RabbitTemplate determineRabbitTemplate(RabbitProducerProperties properties) { RabbitTemplate rabbitTemplate = null; - if (properties.isBatchingEnabled(this.defaultBatchingEnabled)) { + if (properties.isBatchingEnabled()) { BatchingStrategy batchingStrategy = new SimpleBatchingStrategy( - properties.getBatchSize(this.defaultBatchSize), - properties.geteBatchBufferLimit(this.defaultBatchBufferLimit), - properties.getBatchTimeout(this.defaultBatchTimeout)); + properties.getBatchSize(), + properties.getBatchBufferLimit(), + properties.getBatchTimeout()); rabbitTemplate = new BatchingRabbitTemplate(batchingStrategy, getApplicationContext().getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class)); rabbitTemplate.setConnectionFactory(this.connectionFactory); } - if (properties.isCompress(this.defaultCompress)) { + if (properties.isCompress()) { if (rabbitTemplate == null) { rabbitTemplate = new RabbitTemplate(this.connectionFactory); } @@ -641,20 +456,19 @@ public class RabbitMessageChannelBinder extends AbstractBinder { } private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel, - AmqpOutboundEndpoint delegate, RabbitPropertiesAccessor properties) { + AmqpOutboundEndpoint delegate, RabbitProducerProperties properties) { return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties); } private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel, - AmqpOutboundEndpoint delegate, String replyTo, RabbitPropertiesAccessor properties) { + AmqpOutboundEndpoint delegate, String replyTo, RabbitProducerProperties properties) { Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel); MessageHandler handler = new SendingHandler(delegate, replyTo, properties); EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler); consumer.setBeanFactory(getBeanFactory()); consumer.setBeanName("outbound." + name); consumer.afterPropertiesSet(); - DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, properties); - + DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer); consumer.start(); return producerBinding; } @@ -664,15 +478,14 @@ public class RabbitMessageChannelBinder extends AbstractBinder { * queue name because we use default exchange routing by queue name for the original message. * @param queueName The base name for the queue (including the binder prefix, if any). * @param routingKey The routing key for the queue. - * @param properties The properties accessor. + * @param autoBindDlq true if the DLQ should be bound. */ - private void autoBindDLQ(final String queueName, String routingKey, RabbitPropertiesAccessor properties) { + private void autoBindDLQ(final String queueName, String routingKey, String prefix, boolean autoBindDlq) { if (this.logger.isDebugEnabled()) { - this.logger.debug("autoBindDLQ=" + properties.getAutoBindDLQ(this.defaultAutoBindDLQ) + this.logger.debug("autoBindDLQ=" + autoBindDlq + " for: " + queueName); } - if (properties.getAutoBindDLQ(this.defaultAutoBindDLQ)) { - String prefix = properties.getPrefix(this.defaultPrefix); + if (autoBindDlq) { String dlqName = constructDLQName(queueName); Queue dlq = new Queue(dlqName); declareQueue(dlqName, dlq); @@ -787,15 +600,18 @@ public class RabbitMessageChannelBinder extends AbstractBinder { private final String replyTo; + private final RabbitProducerProperties producerProperties; + private final PartitionHandler partitionHandler; - private SendingHandler(MessageHandler delegate, String replyTo, RabbitPropertiesAccessor properties) { + private SendingHandler(MessageHandler delegate, String replyTo, RabbitProducerProperties properties) { this.delegate = delegate; this.replyTo = replyTo; + producerProperties = properties; ConfigurableListableBeanFactory beanFactory = RabbitMessageChannelBinder.this.getBeanFactory(); this.setBeanFactory(beanFactory); this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, - properties, properties.getNextModuleCount()); + properties); } @Override @@ -805,7 +621,7 @@ public class RabbitMessageChannelBinder extends AbstractBinder { if (this.replyTo != null) { messageToSend.put(AmqpHeaders.REPLY_TO, this.replyTo); } - if (this.partitionHandler.isPartitionedModule()) { + if (producerProperties.isPartitioned()) { messageToSend.put(PARTITION_HEADER, this.partitionHandler.determinePartition(message)); } @@ -862,136 +678,4 @@ public class RabbitMessageChannelBinder extends AbstractBinder { } - /** - * Property accessor for the RabbitBinder. Refer to the Spring-AMQP documentation for information on the - * specific properties. - */ - private static class RabbitPropertiesAccessor extends DefaultBindingPropertiesAccessor { - - /** - * The acknowledge mode (i.e. NONE, MANUAL, AUTO). - */ - private static final String ACK_MODE = "ackMode"; - - /** - * The delivery mode (i.e. NON_PERSISTENT, PERSISTENT). - */ - private static final String DELIVERY_MODE = "deliveryMode"; - - /** - * The prefetch count (basic qos). - */ - private static final String PREFETCH = "prefetch"; - - /** - * The prefix for queues, exchanges. - */ - private static final String PREFIX = "prefix"; - - /** - * The reply header patterns. - */ - private static final String REPLY_HEADER_PATTERNS = "replyHeaderPatterns"; - - /** - * The request header patterns. - */ - private static final String REQUEST_HEADER_PATTERNS = "requestHeaderPatterns"; - - /** - * Whether delivery failures should be requeued (boolean). - */ - private static final String REQUEUE = "requeue"; - - /** - * Whether to use transacted channels (boolean). - */ - private static final String TRANSACTED = "transacted"; - - /** - * The number of deliveries between acks. - */ - private static final String TX_SIZE = "txSize"; - - /** - * Whether to automatically declare the DLQ and bind it to the binder DLX (boolean). - */ - private static final String AUTO_BIND_DLQ = "autoBindDLQ"; - - /** - * Whether to automatically declare the DLQ and bind it to the binder DLX (boolean). - */ - private static final String REPUBLISH_TO_DLQ = "republishToDLQ"; - - /** - * Durable pub/sub consumer. - */ - public static final String DURABLE = "durableSubscription"; - - public RabbitPropertiesAccessor(Properties properties) { - super(properties); - } - - public AcknowledgeMode getAcknowledgeMode(AcknowledgeMode defaultValue) { - String ackknowledgeMode = getProperty(ACK_MODE); - if (StringUtils.hasText(ackknowledgeMode)) { - return AcknowledgeMode.valueOf(ackknowledgeMode); - } - else { - return defaultValue; - } - } - - public MessageDeliveryMode getDeliveryMode(MessageDeliveryMode defaultValue) { - String deliveryMode = getProperty(DELIVERY_MODE); - if (StringUtils.hasText(deliveryMode)) { - return MessageDeliveryMode.valueOf(deliveryMode); - } - else { - return defaultValue; - } - } - - public int getPrefetchCount(int defaultValue) { - return getProperty(PREFETCH, defaultValue); - } - - public String getPrefix(String defaultValue) { - return getProperty(PREFIX, defaultValue); - } - - public String[] getReplyHeaderPattens(String[] defaultValue) { - return asStringArray(getProperty(REPLY_HEADER_PATTERNS), defaultValue); - } - - public String[] getRequestHeaderPattens(String[] defaultValue) { - return asStringArray(getProperty(REQUEST_HEADER_PATTERNS), defaultValue); - } - - public boolean getRequeueRejected(boolean defaultValue) { - return getProperty(REQUEUE, defaultValue); - } - - public boolean getTransacted(boolean defaultValue) { - return getProperty(TRANSACTED, defaultValue); - } - - public int getTxSize(int defaultValue) { - return getProperty(TX_SIZE, defaultValue); - } - - public boolean getAutoBindDLQ(boolean defaultValue) { - return getProperty(AUTO_BIND_DLQ, defaultValue); - } - - public boolean getRepublishToDLQ(boolean defaultValue) { - return getProperty(REPUBLISH_TO_DLQ, defaultValue); - } - - public boolean isDurable(boolean defaultValue) { - return getProperty(DURABLE, defaultValue); - } - - } - } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java new file mode 100644 index 000000000..cd88f963e --- /dev/null +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder.rabbit; + +import org.springframework.amqp.core.MessageDeliveryMode; +import org.springframework.cloud.stream.binder.ProducerProperties; + +/** + * @author Marius Bogoevici + */ +public class RabbitProducerProperties extends ProducerProperties { + + private String prefix = ""; + + private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"}; + + private boolean autoBindDlq = false; + + private boolean compress = false; + + private boolean batchingEnabled = false; + + private int batchSize = 100; + + private int batchBufferLimit = 10000; + + private int batchTimeout = 5000; + + private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT; + + private String[] replyHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"}; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public void setRequestHeaderPatterns(String[] requestHeaderPatterns) { + this.requestHeaderPatterns = requestHeaderPatterns; + } + + public String[] getRequestHeaderPatterns() { + return requestHeaderPatterns; + } + + public void setAutoBindDlq(boolean autoBindDlq) { + this.autoBindDlq = autoBindDlq; + } + + public boolean isAutoBindDlq() { + return autoBindDlq; + } + + public void setCompress(boolean compress) { + this.compress = compress; + } + + public boolean isCompress() { + return compress; + } + + public void setDeliveryMode(MessageDeliveryMode deliveryMode) { + this.deliveryMode = deliveryMode; + } + + public MessageDeliveryMode getDeliveryMode() { + return deliveryMode; + } + + public String[] getReplyHeaderPatterns() { + return replyHeaderPatterns; + } + + public void setReplyHeaderPatterns(String[] replyHeaderPatterns) { + this.replyHeaderPatterns = replyHeaderPatterns; + } + + public boolean isBatchingEnabled() { + return batchingEnabled; + } + + public void setBatchingEnabled(boolean batchingEnabled) { + this.batchingEnabled = batchingEnabled; + } + + public int getBatchSize() { + return batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + public int getBatchBufferLimit() { + return batchBufferLimit; + } + + public void setBatchBufferLimit(int batchBufferLimit) { + this.batchBufferLimit = batchBufferLimit; + } + + public int getBatchTimeout() { + return batchTimeout; + } + + public void setBatchTimeout(int batchTimeout) { + this.batchTimeout = batchTimeout; + } + +} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java index d21236442..db829be9c 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java @@ -16,238 +16,95 @@ package org.springframework.cloud.stream.binder.rabbit.config; -import org.springframework.amqp.core.AcknowledgeMode; -import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.io.Resource; /** * @author David Turanski */ -@ConfigurationProperties(prefix = "spring.cloud.stream.binder.rabbit.default") +@ConfigurationProperties(prefix = "spring.cloud.stream.binder.rabbit") class RabbitBinderConfigurationProperties { - private AcknowledgeMode acknowledgeMode; + private String[] addresses = new String[0]; - private int backOffInitialInterval; + private String[] adminAdresses = new String[0]; - private int backOffMaxInterval; + private String[] nodes = new String[0]; - private double backOffMultiplier; + private String username; - private boolean transacted; + private String password; - private boolean concurrency; + private String vhost; - private MessageDeliveryMode defaultDeliveryMode; + private boolean useSSL; - private boolean defaultRequeueRejected; - - private int maxAttempts; - - private int maxConcurrency; - - private int prefetchCount; - - private String prefix; - - private String[] replyHeaderPatterns; - - private String[] requestHeaderPatterns; - - private int txSize; - - private boolean autoBindDLQ; - - private boolean republishToDLQ; - - private boolean batchingEnabled; - - private int batchSize; - - private int batchBufferLimit; - - private int batchTimeout; - - private boolean compress; + private Resource sslPropertiesLocation; private int compressionLevel; - private boolean durableSubscription = true; - - public AcknowledgeMode getAcknowledgeMode() { - return acknowledgeMode; + public String[] getAddresses() { + return addresses; } - public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; + public void setAddresses(String[] addresses) { + this.addresses = addresses; } - public int getBackOffInitialInterval() { - return backOffInitialInterval; + public String[] getAdminAdresses() { + return adminAdresses; } - public void setBackOffInitialInterval(int backOffInitialInterval) { - this.backOffInitialInterval = backOffInitialInterval; + public void setAdminAdresses(String[] adminAdresses) { + this.adminAdresses = adminAdresses; } - public int getBackOffMaxInterval() { - return backOffMaxInterval; + public String[] getNodes() { + return nodes; } - public void setBackOffMaxInterval(int backOffMaxInterval) { - this.backOffMaxInterval = backOffMaxInterval; + public void setNodes(String[] nodes) { + this.nodes = nodes; } - public double getBackOffMultiplier() { - return backOffMultiplier; + public String getUsername() { + return username; } - public void setBackOffMultiplier(double backOffMultiplier) { - this.backOffMultiplier = backOffMultiplier; + public void setUsername(String username) { + this.username = username; } - public boolean isTransacted() { - return transacted; + public String getPassword() { + return password; } - public void setTransacted(boolean transacted) { - this.transacted = transacted; + public void setPassword(String password) { + this.password = password; } - public boolean isConcurrency() { - return concurrency; + public String getVhost() { + return vhost; } - public void setConcurrency(boolean concurrency) { - this.concurrency = concurrency; + public void setVhost(String vhost) { + this.vhost = vhost; } - public MessageDeliveryMode getDefaultDeliveryMode() { - return defaultDeliveryMode; + public boolean isUseSSL() { + return useSSL; } - public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) { - this.defaultDeliveryMode = defaultDeliveryMode; + public void setUseSSL(boolean useSSL) { + this.useSSL = useSSL; } - public boolean isDefaultRequeueRejected() { - return defaultRequeueRejected; + public Resource getSslPropertiesLocation() { + return sslPropertiesLocation; } - public void setDefaultRequeueRejected(boolean defaultRequeueRejected) { - this.defaultRequeueRejected = defaultRequeueRejected; - } - - public int getMaxAttempts() { - return maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public int getMaxConcurrency() { - return maxConcurrency; - } - - public void setMaxConcurrency(int maxConcurrency) { - this.maxConcurrency = maxConcurrency; - } - - public int getPrefetchCount() { - return prefetchCount; - } - - public void setPrefetchCount(int prefetchCount) { - this.prefetchCount = prefetchCount; - } - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public String[] getReplyHeaderPatterns() { - return replyHeaderPatterns; - } - - public void setReplyHeaderPatterns(String[] replyHeaderPatterns) { - this.replyHeaderPatterns = replyHeaderPatterns; - } - - public String[] getRequestHeaderPatterns() { - return requestHeaderPatterns; - } - - public void setRequestHeaderPatterns(String[] requestHeaderPatterns) { - this.requestHeaderPatterns = requestHeaderPatterns; - } - - public int getTxSize() { - return txSize; - } - - public void setTxSize(int txSize) { - this.txSize = txSize; - } - - public boolean isAutoBindDLQ() { - return autoBindDLQ; - } - - public void setAutoBindDLQ(boolean autoBindDLQ) { - this.autoBindDLQ = autoBindDLQ; - } - - public boolean isRepublishToDLQ() { - return republishToDLQ; - } - - public void setRepublishToDLQ(boolean republishToDLQ) { - this.republishToDLQ = republishToDLQ; - } - - public boolean isBatchingEnabled() { - return batchingEnabled; - } - - public void setBatchingEnabled(boolean batchingEnabled) { - this.batchingEnabled = batchingEnabled; - } - - public int getBatchSize() { - return batchSize; - } - - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } - - public int getBatchBufferLimit() { - return batchBufferLimit; - } - - public void setBatchBufferLimit(int batchBufferLimit) { - this.batchBufferLimit = batchBufferLimit; - } - - public int getBatchTimeout() { - return batchTimeout; - } - - public void setBatchTimeout(int batchTimeout) { - this.batchTimeout = batchTimeout; - } - - public boolean isCompress() { - return compress; - } - - public void setCompress(boolean compress) { - this.compress = compress; + public void setSslPropertiesLocation(Resource sslPropertiesLocation) { + this.sslPropertiesLocation = sslPropertiesLocation; } public int getCompressionLevel() { @@ -257,12 +114,4 @@ class RabbitBinderConfigurationProperties { public void setCompressionLevel(int compressionLevel) { this.compressionLevel = compressionLevel; } - - public boolean isDurableSubscription() { - return durableSubscription; - } - - public void setDurableSubscription(boolean durableSubscription) { - this.durableSubscription = durableSubscription; - } } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java index 14dff362c..412be37c0 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java @@ -45,63 +45,43 @@ public class RabbitMessageChannelBinderConfiguration { @Autowired private ConnectionFactory rabbitConnectionFactory; - + @Autowired private RabbitBinderConfigurationProperties rabbitBinderConfigurationProperties; - - @Autowired - private SpringRabbitMQProperties springRabbitMQProperties; @Bean RabbitMessageChannelBinder rabbitMessageChannelBinder() { RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(rabbitConnectionFactory); binder.setCodec(codec); - binder.setAddresses(springRabbitMQProperties.getAddresses()); - binder.setAdminAddresses(springRabbitMQProperties.getAdminAdresses()); + binder.setAddresses(rabbitBinderConfigurationProperties.getAddresses()); + binder.setAdminAddresses(rabbitBinderConfigurationProperties.getAdminAdresses()); binder.setCompressingPostProcessor(gZipPostProcessor()); binder.setDecompressingPostProcessor(deCompressingPostProcessor()); - binder.setDefaultAcknowledgeMode(rabbitBinderConfigurationProperties.getAcknowledgeMode()); - binder.setDefaultAutoBindDLQ(rabbitBinderConfigurationProperties.isAutoBindDLQ()); - binder.setDefaultChannelTransacted(rabbitBinderConfigurationProperties.isTransacted()); - binder.setDefaultDefaultDeliveryMode(rabbitBinderConfigurationProperties.getDefaultDeliveryMode()); - binder.setDefaultDefaultRequeueRejected(rabbitBinderConfigurationProperties.isDefaultRequeueRejected()); - binder.setDefaultMaxConcurrency(rabbitBinderConfigurationProperties.getMaxConcurrency()); - binder.setDefaultPrefetchCount(rabbitBinderConfigurationProperties.getPrefetchCount()); - binder.setDefaultPrefix(rabbitBinderConfigurationProperties.getPrefix()); - binder.setDefaultReplyHeaderPatterns(rabbitBinderConfigurationProperties.getReplyHeaderPatterns()); - binder.setDefaultRepublishToDLQ(rabbitBinderConfigurationProperties.isRepublishToDLQ()); - binder.setDefaultRequestHeaderPatterns(rabbitBinderConfigurationProperties.getRequestHeaderPatterns()); - binder.setDefaultTxSize(rabbitBinderConfigurationProperties.getTxSize()); - binder.setNodes(springRabbitMQProperties.getNodes()); - binder.setPassword(springRabbitMQProperties.getPassword()); - binder.setSslPropertiesLocation(springRabbitMQProperties.getSslPropertiesLocation()); - binder.setUsername(springRabbitMQProperties.getUsername()); - binder.setUseSSL(springRabbitMQProperties.isUseSSL()); - binder.setVhost(springRabbitMQProperties.getVhost()); - binder.setDefaultDurableSubscription(rabbitBinderConfigurationProperties.isDurableSubscription()); + binder.setNodes(rabbitBinderConfigurationProperties.getNodes()); + binder.setPassword(rabbitBinderConfigurationProperties.getPassword()); + binder.setSslPropertiesLocation(rabbitBinderConfigurationProperties.getSslPropertiesLocation()); + binder.setUsername(rabbitBinderConfigurationProperties.getUsername()); + binder.setUseSSL(rabbitBinderConfigurationProperties.isUseSSL()); + binder.setVhost(rabbitBinderConfigurationProperties.getVhost()); return binder; } - + @Bean MessagePostProcessor deCompressingPostProcessor() { return new DelegatingDecompressingPostProcessor(); } - + @Bean MessagePostProcessor gZipPostProcessor() { GZipPostProcessor gZipPostProcessor = new GZipPostProcessor(); gZipPostProcessor.setLevel(rabbitBinderConfigurationProperties.getCompressionLevel()); - return gZipPostProcessor; + return gZipPostProcessor; } - + @Bean ConnectionFactorySettings rabbitConnectionFactorySettings() { return new ConnectionFactorySettings(); } - - @Bean - SpringRabbitMQProperties springRabbitMQProperties() { - return new SpringRabbitMQProperties(); - } } + diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java index c0385be7c..634e30392 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.PropertySource; /** * Bind to services, either locally or in a cloud environment. @@ -47,7 +46,6 @@ import org.springframework.context.annotation.PropertySource; @ConditionalOnMissingBean(Binder.class) @Import(RabbitMessageChannelBinderConfiguration.class) @AutoConfigureBefore({CloudAutoConfiguration.class, RabbitAutoConfiguration.class}) -@PropertySource("classpath:/META-INF/spring-cloud-stream/rabbit-binder.properties") public class RabbitServiceAutoConfiguration { @Configuration diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/SpringRabbitMQProperties.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/SpringRabbitMQProperties.java deleted file mode 100644 index d92bfa132..000000000 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/SpringRabbitMQProperties.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binder.rabbit.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.io.Resource; - -/** - * @author David Turanski - */ -@ConfigurationProperties(prefix = "spring.rabbitmq") -class SpringRabbitMQProperties { - - private String[] addresses = new String[0]; - - private String[] adminAdresses = new String[0]; - - private String[] nodes = new String[0]; - - private String username; - - private String password; - - private String vhost; - - private boolean useSSL; - - private Resource sslPropertiesLocation; - - public String[] getAddresses() { - return addresses; - } - - public void setAddresses(String[] addresses) { - this.addresses = addresses; - } - - public String[] getAdminAdresses() { - return adminAdresses; - } - - public void setAdminAdresses(String[] adminAdresses) { - this.adminAdresses = adminAdresses; - } - - public String[] getNodes() { - return nodes; - } - - public void setNodes(String[] nodes) { - this.nodes = nodes; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getVhost() { - return vhost; - } - - public void setVhost(String vhost) { - this.vhost = vhost; - } - - public boolean isUseSSL() { - return useSSL; - } - - public void setUseSSL(boolean useSSL) { - this.useSSL = useSSL; - } - - public Resource getSslPropertiesLocation() { - return sslPropertiesLocation; - } - - public void setSslPropertiesLocation(Resource sslPropertiesLocation) { - this.sslPropertiesLocation = sslPropertiesLocation; - } -} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring-cloud-stream/rabbit-binder.properties b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring-cloud-stream/rabbit-binder.properties deleted file mode 100644 index 2a89ee8c2..000000000 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring-cloud-stream/rabbit-binder.properties +++ /dev/null @@ -1,23 +0,0 @@ -spring.cloud.stream.binder.rabbit.default.acknowledgeMode: AUTO -spring.cloud.stream.binder.rabbit.default.autoBindDLQ: false -spring.cloud.stream.binder.rabbit.default.backOffInitialInterval: 1000 -spring.cloud.stream.binder.rabbit.default.backOffMaxInterval: 10000 -spring.cloud.stream.binder.rabbit.default.backOffMultiplier: 2.0 -spring.cloud.stream.binder.rabbit.default.batchBufferLimit: 10000 -spring.cloud.stream.binder.rabbit.default.batchingEnabled: false -spring.cloud.stream.binder.rabbit.default.batchSize: 100 -spring.cloud.stream.binder.rabbit.default.batchTimeout: 5000 -spring.cloud.stream.binder.rabbit.default.compress: false -spring.cloud.stream.binder.rabbit.default.concurrency: 1 -spring.cloud.stream.binder.rabbit.default.defaultDeliveryMode: PERSISTENT -spring.cloud.stream.binder.rabbit.default.durableSubscription: false -spring.cloud.stream.binder.rabbit.default.maxAttempts: 3 -spring.cloud.stream.binder.rabbit.default.maxConcurrency: 1 -spring.cloud.stream.binder.rabbit.default.prefix: binder. -spring.cloud.stream.binder.rabbit.default.prefetch: 1 -spring.cloud.stream.binder.rabbit.default.replyHeaderPatterns: STANDARD_REPLY_HEADERS,* -spring.cloud.stream.binder.rabbit.default.republishToDLQ: false -spring.cloud.stream.binder.rabbit.default.requestHeaderPatterns: STANDARD_REQUEST_HEADERS,* -spring.cloud.stream.binder.rabbit.default.defaultRequeueRejected: true -spring.cloud.stream.binder.rabbit.default.transacted:false -spring.cloud.stream.binder.rabbit.default.txSize: 1 diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java index b971c8214..1b2cb9c0c 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java @@ -33,7 +33,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; @@ -55,11 +54,11 @@ import org.springframework.amqp.support.AmqpHeaders; import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor; import org.springframework.amqp.utils.test.TestUtils; import org.springframework.beans.DirectFieldAccessor; -import org.springframework.cloud.stream.binder.AbstractTestBinder; -import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.PartitionCapableBinderTests; +import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy; +import org.springframework.cloud.stream.binder.PartitionSelectorStrategy; +import org.springframework.cloud.stream.binder.PartitionTestSupport; import org.springframework.cloud.stream.binder.Spy; import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport; import org.springframework.context.ApplicationContext; @@ -80,7 +79,7 @@ import org.springframework.messaging.support.GenericMessage; * @author Gary Russell * @author David Turanski */ -public class RabbitBinderTests extends PartitionCapableBinderTests { +public class RabbitBinderTests extends PartitionCapableBinderTests { private final String CLASS_UNDER_TEST_NAME = RabbitMessageChannelBinder.class.getSimpleName(); @@ -90,13 +89,23 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport(); @Override - protected Binder getBinder() { + protected RabbitTestBinder getBinder() { if (testBinder == null) { testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource()); } return testBinder; } + @Override + protected RabbitConsumerProperties createConsumerProperties() { + return new RabbitConsumerProperties(); + } + + @Override + protected RabbitProducerProperties createProducerProperties() { + return new RabbitProducerProperties(); + } + @Override protected boolean usesExplicitRouting() { return true; @@ -104,11 +113,11 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testSendAndReceiveBad() throws Exception { - Binder binder = getBinder(); + RabbitTestBinder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); DirectChannel moduleInputChannel = new DirectChannel(); - Binding producerBinding = binder.bindProducer("bad.0", moduleOutputChannel, null); - Binding consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel, null); + Binding producerBinding = binder.bindProducer("bad.0", moduleOutputChannel, new RabbitProducerProperties()); + Binding consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel, new RabbitConsumerProperties()); Message message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar").build(); final CountDownLatch latch = new CountDownLatch(3); @@ -128,16 +137,16 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testConsumerProperties() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("transacted", "true"); // test transacted with defaults; not allowed with ackmode NONE + RabbitTestBinder binder = getBinder(); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setTransacted(true); Binding consumerBinding = binder.bindConsumer("props.0", null, new DirectChannel(), properties); AbstractEndpoint endpoint = extractEndpoint(consumerBinding); SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer", SimpleMessageListenerContainer.class); assertEquals(AcknowledgeMode.AUTO, container.getAcknowledgeMode()); assertThat(container.getQueueNames()[0], - startsWith(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX)); + startsWith(properties.getPrefix())); assertTrue(TestUtils.getPropertyValue(container, "transactional", Boolean.class)); assertEquals(1, TestUtils.getPropertyValue(container, "concurrentConsumers")); assertNull(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")); @@ -152,20 +161,20 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { consumerBinding.unbind(); assertFalse(endpoint.isRunning()); - properties = new Properties(); - properties.put("ackMode", "NONE"); - properties.put("backOffInitialInterval", "2000"); - properties.put("backOffMaxInterval", "20000"); - properties.put("backOffMultiplier", "5.0"); - properties.put("concurrency", "2"); - properties.put("maxAttempts", "23"); - properties.put("maxConcurrency", "3"); - properties.put("prefix", "foo."); - properties.put("prefetch", "20"); - properties.put("requestHeaderPatterns", "foo"); - properties.put("requeue", "false"); - properties.put("txSize", "10"); - properties.put("partitionIndex", 0); + properties = new RabbitConsumerProperties(); + properties.setAcknowledgeMode(AcknowledgeMode.NONE); + properties.setBackOffInitialInterval(2000); + properties.setBackOffMaxInterval(20000); + properties.setBackOffMultiplier(5.0); + properties.setConcurrency(2); + properties.setMaxAttempts(23); + properties.setMaxConcurrency(3); + properties.setPrefix("foo."); + properties.setPrefetch(20); + properties.setRequestHeaderPatterns(new String[] {"foo"}); + properties.setRequeueRejected(false); + properties.setTxSize(10); + properties.setInstanceIndex(0); consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties); endpoint = extractEndpoint(consumerBinding); @@ -179,8 +188,8 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testProducerProperties() throws Exception { - Binder binder = getBinder(); - Binding producerBinding = binder.bindProducer("props.0", new DirectChannel(), null); + RabbitTestBinder binder = getBinder(); + Binding producerBinding = binder.bindProducer("props.0", new DirectChannel(), new RabbitProducerProperties()); @SuppressWarnings("unchecked") AbstractEndpoint endpoint = extractEndpoint(producerBinding); MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode", @@ -192,15 +201,15 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { producerBinding.unbind(); assertFalse(endpoint.isRunning()); - Properties properties = new Properties(); - properties.put("prefix", "foo."); - properties.put("deliveryMode", "NON_PERSISTENT"); - properties.put("requestHeaderPatterns", "foo"); - properties.put("partitionKeyExpression", "'foo'"); - properties.put("partitionKeyExtractorClass", "foo"); - properties.put("partitionSelectorExpression", "0"); - properties.put("partitionSelectorClass", "foo"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1"); + RabbitProducerProperties properties = new RabbitProducerProperties(); + properties.setPrefix("foo."); + properties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); + properties.setRequestHeaderPatterns(new String[] {"foo"}); + properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'")); + properties.setPartitionKeyExtractorClass(TestPartitionKeyExtractorClass.class); + properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0")); + properties.setPartitionSelectorClass(TestPartitionSelectorClass.class); + properties.setPartitionCount(1); producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties); endpoint = extractEndpoint(producerBinding); @@ -221,14 +230,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { public void testDurablePubSubWithAutoBindDLQ() throws Exception { RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource()); - Binder binder = getBinder(); + RabbitTestBinder binder = getBinder(); - Properties properties = new Properties(); - properties.put("prefix", TEST_PREFIX); - properties.put("autoBindDLQ", "true"); - properties.put("durableSubscription", "true"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setPrefix(TEST_PREFIX); + properties.setAutoBindDlq(true); + properties.setDurableSubscription(true); + properties.setMaxAttempts(1); // disable retry + properties.setRequeueRejected(false); DirectChannel moduleInputChannel = new DirectChannel(); moduleInputChannel.setBeanName("durableTest"); moduleInputChannel.subscribe(new MessageHandler() { @@ -263,13 +272,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { public void testNonDurablePubSubWithAutoBindDLQ() throws Exception { RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource()); - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("prefix", TEST_PREFIX); - properties.put("autoBindDLQ", "true"); - properties.put("durableSubscription", "false"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); + RabbitTestBinder binder = getBinder(); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setPrefix(TEST_PREFIX); + properties.setAutoBindDlq(true); + properties.setDurableSubscription(false); + properties.setMaxAttempts(1); // disable retry + properties.setRequeueRejected(false); DirectChannel moduleInputChannel = new DirectChannel(); moduleInputChannel.setBeanName("nondurabletest"); moduleInputChannel.subscribe(new MessageHandler() { @@ -288,13 +297,13 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testAutoBindDLQ() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("prefix", TEST_PREFIX); - properties.put("autoBindDLQ", "true"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); - properties.put("durableSubscription","true"); + RabbitTestBinder binder = getBinder(); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setPrefix(TEST_PREFIX); + properties.setAutoBindDlq(true); + properties.setMaxAttempts(1); // disable retry + properties.setRequeueRejected(false); + properties.setDurableSubscription(true); DirectChannel moduleInputChannel = new DirectChannel(); moduleInputChannel.setBeanName("dlqTest"); moduleInputChannel.subscribe(new MessageHandler() { @@ -333,33 +342,34 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testAutoBindDLQPartionedConsumerFirst() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("prefix", "bindertest."); - properties.put("autoBindDLQ", "true"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); - properties.put("partitionIndex", "0"); + RabbitTestBinder binder = getBinder(); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setPrefix("bindertest."); + properties.setAutoBindDlq(true); + properties.setMaxAttempts(1); // disable retry + properties.setRequeueRejected(false); + properties.setPartitioned(true); + properties.setInstanceIndex(0); DirectChannel input0 = new DirectChannel(); input0.setBeanName("test.input0DLQ"); Binding input0Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input0, properties); Binding defaultConsumerBinding1 = binder.bindConsumer("partDLQ.0", "default", new QueueChannel(), properties); - properties.put("partitionIndex", "1"); + properties.setInstanceIndex(1); DirectChannel input1 = new DirectChannel(); input1.setBeanName("test.input1DLQ"); Binding input1Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input1, properties); Binding defaultConsumerBinding2 = binder.bindConsumer("partDLQ.0", "default", new QueueChannel(), properties); - properties.clear(); - properties.put("prefix", "bindertest."); - properties.put("autoBindDLQ", "true"); - properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "2"); + RabbitProducerProperties producerProperties = new RabbitProducerProperties(); + producerProperties.setPrefix("bindertest."); + producerProperties.setAutoBindDlq(true); + producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class); + producerProperties.setPartitionSelectorClass(PartitionTestSupport.class); + producerProperties.setPartitionCount(2); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); - Binding outputBinding = binder.bindProducer("partDLQ.0", output, properties); + Binding outputBinding = binder.bindProducer("partDLQ.0", output, producerProperties); final CountDownLatch latch0 = new CountDownLatch(1); input0.subscribe(new MessageHandler() { @@ -417,35 +427,36 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { } @Test - public void testAutoBindDLQPartionedProducerFirst() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); + public void testAutoBindDLQPartitionedProducerFirst() throws Exception { + RabbitTestBinder binder = getBinder(); + RabbitProducerProperties properties = new RabbitProducerProperties(); - properties.put("prefix", "bindertest."); - properties.put("autoBindDLQ", "true"); - properties.put("requiredGroups", "dlqPartGrp"); - properties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - properties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "2"); + properties.setPrefix("bindertest."); + properties.setAutoBindDlq(true); + properties.setRequiredGroups("dlqPartGrp"); + properties.setPartitionKeyExtractorClass(PartitionTestSupport.class); + properties.setPartitionSelectorClass(PartitionTestSupport.class); + properties.setPartitionCount(2); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); Binding outputBinding = binder.bindProducer("partDLQ.1", output, properties); - properties.clear(); - properties.put("prefix", "bindertest."); - properties.put("autoBindDLQ", "true"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); - properties.put("partitionIndex", "0"); + RabbitConsumerProperties consumerProperties = new RabbitConsumerProperties(); + consumerProperties.setPrefix("bindertest."); + consumerProperties.setAutoBindDlq(true); + consumerProperties.setMaxAttempts(1); // disable retry + consumerProperties.setRequeueRejected(false); + consumerProperties.setPartitioned(true); + consumerProperties.setInstanceIndex(0); DirectChannel input0 = new DirectChannel(); input0.setBeanName("test.input0DLQ"); - Binding input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0, properties); - Binding defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), properties); - properties.put("partitionIndex", "1"); + Binding input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0, consumerProperties); + Binding defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), consumerProperties); + consumerProperties.setInstanceIndex(1); DirectChannel input1 = new DirectChannel(); input1.setBeanName("test.input1DLQ"); - Binding input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1, properties); - Binding defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), properties); + Binding input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1, consumerProperties); + Binding defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer", new QueueChannel(), consumerProperties); final CountDownLatch latch0 = new CountDownLatch(1); input0.subscribe(new MessageHandler() { @@ -512,14 +523,14 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args); admin.declareQueue(queue); - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("prefix", TEST_PREFIX); - properties.put("autoBindDLQ", "true"); - properties.put("republishToDLQ", "true"); - properties.put("maxAttempts", "1"); // disable retry - properties.put("requeue", "false"); - properties.put("durableSubscription", "true"); + RabbitTestBinder binder = getBinder(); + RabbitConsumerProperties properties = new RabbitConsumerProperties(); + properties.setPrefix(TEST_PREFIX); + properties.setAutoBindDlq(true); + properties.setRepublishToDlq(true); + properties.setMaxAttempts(1); // disable retry + properties.setRequeueRejected(false); + properties.setDurableSubscription(true); DirectChannel moduleInputChannel = new DirectChannel(); moduleInputChannel.setBeanName("dlqPubTest"); moduleInputChannel.subscribe(new MessageHandler() { @@ -554,21 +565,21 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { @Test public void testBatchingAndCompression() throws Exception { RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource()); - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("deliveryMode", "NON_PERSISTENT"); - properties.put("batchingEnabled", "true"); - properties.put("batchSize", "2"); - properties.put("batchBufferLimit", "100000"); - properties.put("batchTimeout", "30000"); - properties.put("compress", "true"); - properties.put("requiredGroups", "default"); + RabbitTestBinder binder = getBinder(); + RabbitProducerProperties properties = new RabbitProducerProperties(); + properties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); + properties.setBatchingEnabled(true); + properties.setBatchSize(2); + properties.setBatchBufferLimit(100000); + properties.setBatchTimeout(30000); + properties.setCompress(true); + properties.setRequiredGroups("default"); DirectChannel output = new DirectChannel(); output.setBeanName("batchingProducer"); Binding producerBinding = binder.bindProducer("batching.0", output, properties); - while (template.receive(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + "batching.0.default") != null) { + while (template.receive(properties.getPrefix() + "batching.0.default") != null) { } Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class)); @@ -591,7 +602,7 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { QueueChannel input = new QueueChannel(); input.setBeanName("batchingConsumer"); - Binding consumerBinding = binder.bindConsumer("batching.0", "test", input, null); + Binding consumerBinding = binder.bindConsumer("batching.0", "test", input, new RabbitConsumerProperties()); output.send(new GenericMessage<>("foo".getBytes())); output.send(new GenericMessage<>("bar".getBytes())); @@ -617,51 +628,56 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy(); CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort()); RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf); - rabbitBinder.setDefaultAutoBindDLQ(true); - AbstractTestBinder binder = new RabbitTestBinder(cf, rabbitBinder); + RabbitTestBinder binder = new RabbitTestBinder(cf, rabbitBinder); - Properties properties = new Properties(); - properties.put("prefix", "latebinder."); + RabbitProducerProperties properties = new RabbitProducerProperties(); + properties.setPrefix("latebinder."); + properties.setAutoBindDlq(true); MessageChannel moduleOutputChannel = new DirectChannel(); Binding late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel, properties); QueueChannel moduleInputChannel = new QueueChannel(); - Binding late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel, properties); + RabbitConsumerProperties rabbitConsumerProperties = new RabbitConsumerProperties(); + rabbitConsumerProperties.setPrefix("latebinder."); + Binding late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel, rabbitConsumerProperties); - properties.put("partitionKeyExpression", "payload.equals('0') ? 0 : 1"); - properties.put("partitionSelectorExpression", "hashCode()"); - properties.put("nextModuleCount", "2"); + properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload.equals('0') ? 0 : 1")); + properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()")); + properties.setPartitionCount(2); MessageChannel partOutputChannel = new DirectChannel(); Binding partlate0ProducerBinding = binder.bindProducer("partlate.0", partOutputChannel, properties); QueueChannel partInputChannel0 = new QueueChannel(); QueueChannel partInputChannel1 = new QueueChannel(); - properties.clear(); - properties.put("prefix", "latebinder."); - properties.put("partitionIndex", "0"); - Binding partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0, properties); - properties.put("partitionIndex", "1"); - Binding partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1, properties); - rabbitBinder.setDefaultAutoBindDLQ(false); - properties.clear(); - properties.put("prefix", "latebinder."); + RabbitConsumerProperties partLateConsumerProperties = new RabbitConsumerProperties(); + partLateConsumerProperties.setPrefix("latebinder."); + partLateConsumerProperties.setPartitioned(true); + partLateConsumerProperties.setInstanceIndex(0); + Binding partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0, partLateConsumerProperties); + partLateConsumerProperties.setInstanceIndex(1); + Binding partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1, partLateConsumerProperties); + + RabbitProducerProperties noDlqProducerProperties = new RabbitProducerProperties(); + noDlqProducerProperties.setPrefix("latebinder."); MessageChannel noDLQOutputChannel = new DirectChannel(); - Binding noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, properties); + Binding noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel, noDlqProducerProperties); QueueChannel noDLQInputChannel = new QueueChannel(); - Binding noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel, properties); + RabbitConsumerProperties noDlqConsumerProperties = new RabbitConsumerProperties(); + noDlqConsumerProperties.setPrefix("latebinder."); + Binding noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel, noDlqConsumerProperties); MessageChannel outputChannel = new DirectChannel(); - Binding pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel, properties); + Binding pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel, noDlqProducerProperties); QueueChannel pubSubInputChannel = new QueueChannel(); - properties.setProperty("durableSubscription", "false"); - Binding nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup", pubSubInputChannel, properties); + noDlqConsumerProperties.setDurableSubscription(false); + Binding nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup", pubSubInputChannel, noDlqConsumerProperties); QueueChannel durablePubSubInputChannel = new QueueChannel(); - properties.setProperty("durableSubscription", "true"); - Binding durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup", durablePubSubInputChannel, properties); + noDlqConsumerProperties.setDurableSubscription(true); + Binding durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup", durablePubSubInputChannel, noDlqConsumerProperties); proxy.start(); @@ -781,12 +797,12 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { public Object receive(boolean expectNull) throws Exception { if (expectNull) { Thread.sleep(50); - return template.receiveAndConvert(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + queue); + return template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue); } Object bar = null; int n = 0; while (n++ < 100 && bar == null) { - bar = template.receiveAndConvert(RabbitMessageChannelBinder.DEFAULT_RABBIT_PREFIX + queue); + bar = template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue); Thread.sleep(100); } assertTrue("Message did not arrive in RabbitMQ", n < 100); @@ -796,4 +812,20 @@ public class RabbitBinderTests extends PartitionCapableBinderTests { }; } + private static class TestPartitionKeyExtractorClass implements PartitionKeyExtractorStrategy { + + @Override + public Object extractKey(Message message) { + return null; + } + } + + private static class TestPartitionSelectorClass implements PartitionSelectorStrategy { + + @Override + public int selectPartition(Object key, int partitionCount) { + return 0; + } + } + } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java index 653e0c755..81b40def5 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java @@ -17,7 +17,6 @@ package org.springframework.cloud.stream.binder.rabbit; import java.util.HashSet; -import java.util.Properties; import java.util.Set; import org.springframework.amqp.rabbit.connection.ConnectionFactory; @@ -38,9 +37,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; * @author David Turanski * @author Mark Fisher */ -public class RabbitTestBinder extends AbstractTestBinder { - - public static final String BINDER_PREFIX = "binder."; +public class RabbitTestBinder extends AbstractTestBinder { private final RabbitAdmin rabbitAdmin; @@ -68,32 +65,21 @@ public class RabbitTestBinder extends AbstractTestBinder bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) { + public Binding bindConsumer(String name, String group, MessageChannel moduleInputChannel, RabbitConsumerProperties properties) { if (group != null) { - this.queues.add(prefix(properties) + name + ("." + group)); + this.queues.add(properties.getPrefix() + name + ("." + group)); } - this.exchanges.add(prefix(properties) + name); + this.exchanges.add(properties.getPrefix() + name); return super.bindConsumer(name, group, moduleInputChannel, properties); } @Override - public Binding bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) { - this.queues.add(prefix(properties) + name + ".default"); - this.exchanges.add(prefix(properties) + name); + public Binding bindProducer(String name, MessageChannel moduleOutputChannel, RabbitProducerProperties properties) { + this.queues.add(properties.getPrefix() + name + ".default"); + this.exchanges.add(properties.getPrefix() + name); return super.bindProducer(name, moduleOutputChannel, properties); } - public String prefix(Properties properties) { - if (properties != null) { - String prefix = properties.getProperty("prefix"); - if (prefix != null) { - this.prefixes.add(prefix); - return prefix; - } - } - return BINDER_PREFIX; - } - @Override public void cleanup() { for (String queue : this.queues) { diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java index bac0a9b63..d7ea1c6fe 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java @@ -82,7 +82,7 @@ public class RabbitBinderModuleTests { public void testParentConnectionFactoryInheritedByDefault() { context = SpringApplication.run(SimpleProcessor.class, "--server.port=0"); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RabbitMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); ConnectionFactory binderConnectionFactory = @@ -105,7 +105,7 @@ public class RabbitBinderModuleTests { public void testParentConnectionFactoryInheritedIfOverridden() { context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run("--server.port=0"); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RabbitMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); ConnectionFactory binderConnectionFactory = @@ -135,7 +135,7 @@ public class RabbitBinderModuleTests { params.add("--server.port=0"); context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[params.size()])); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RabbitMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); ConnectionFactory binderConnectionFactory = diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/RedisMessageChannelBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/RedisMessageChannelBinder.java index d5836ae8b..c1d12fda5 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/RedisMessageChannelBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/RedisMessageChannelBinder.java @@ -21,24 +21,22 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; import java.util.UUID; -import org.springframework.cloud.stream.binder.AbstractBinder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.cloud.stream.binder.AbstractBinder; import org.springframework.cloud.stream.binder.BinderHeaders; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.DefaultBinding; -import org.springframework.cloud.stream.binder.DefaultBindingPropertiesAccessor; import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter; import org.springframework.cloud.stream.binder.MessageValues; import org.springframework.cloud.stream.binder.PartitionHandler; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; @@ -68,7 +66,7 @@ import org.springframework.util.StringUtils; * @author David Turanski * @author Jennifer Hickey */ -public class RedisMessageChannelBinder extends AbstractBinder { +public class RedisMessageChannelBinder extends AbstractBinder { private static final String ERROR_HEADER = "errorKey"; @@ -80,25 +78,6 @@ public class RedisMessageChannelBinder extends AbstractBinder { private final RedisOperations redisOperations; - /** - * Retry + concurrency + partitioning. - */ - private static final Set SUPPORTED_CONSUMER_PROPERTIES = new SetBuilder() - .addAll(CONSUMER_STANDARD_PROPERTIES) - .addAll(CONSUMER_RETRY_PROPERTIES) - .add(BinderPropertyKeys.CONCURRENCY) - .add(BinderPropertyKeys.PARTITION_INDEX) - .build(); - - /** - * Partitioning. - */ - private static final Set SUPPORTED_PRODUCER_PROPERTIES = new SetBuilder() - .addAll(PRODUCER_PARTITIONING_PROPERTIES) - .addAll(PRODUCER_STANDARD_PROPERTIES) - .add(BinderPropertyKeys.REQUIRED_GROUPS) - .build(); - private final RedisConnectionFactory connectionFactory; private final EmbeddedHeadersMessageConverter embeddedHeadersMessageConverter = new @@ -139,24 +118,21 @@ public class RedisMessageChannelBinder extends AbstractBinder { } @Override - protected Binding doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, Properties properties) { + protected Binding doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, ConsumerProperties properties) { if (!StringUtils.hasText(group)) { group = "anonymous." + UUID.randomUUID().toString(); } - RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties); String queueName = groupedName(name, group); - validateConsumerProperties(queueName, properties, SUPPORTED_CONSUMER_PROPERTIES); - int partitionIndex = accessor.getPartitionIndex(); - if (partitionIndex >= 0) { - queueName += "-" + partitionIndex; + if (properties.isPartitioned()) { + queueName += "-" + properties.getInstanceIndex(); } - MessageProducerSupport adapter = createInboundAdapter(accessor, queueName); - return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, accessor); + MessageProducerSupport adapter = createInboundAdapter(properties, queueName); + return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, properties); } - private MessageProducerSupport createInboundAdapter(RedisPropertiesAccessor accessor, String queueName) { + private MessageProducerSupport createInboundAdapter(ConsumerProperties accessor, String queueName) { MessageProducerSupport adapter; - int concurrency = accessor.getConcurrency(this.defaultConcurrency); + int concurrency = accessor.getConcurrency(); concurrency = concurrency > 0 ? concurrency : 1; if (concurrency == 1) { RedisQueueMessageDrivenEndpoint single = new RedisQueueMessageDrivenEndpoint(queueName, @@ -172,7 +148,7 @@ public class RedisMessageChannelBinder extends AbstractBinder { } private Binding doRegisterConsumer(String bindingName, String group, String channelName, MessageChannel moduleInputChannel, - MessageProducerSupport adapter, final RedisPropertiesAccessor properties) { + MessageProducerSupport adapter, final ConsumerProperties properties) { DirectChannel bridgeToModuleChannel = new DirectChannel(); bridgeToModuleChannel.setBeanFactory(this.getBeanFactory()); bridgeToModuleChannel.setBeanName(channelName + ".bridge"); @@ -180,7 +156,7 @@ public class RedisMessageChannelBinder extends AbstractBinder { adapter.setOutputChannel(bridgeInputChannel); adapter.setBeanName("inbound." + channelName); adapter.afterPropertiesSet(); - DefaultBinding consumerBinding = new DefaultBinding(bindingName, group, moduleInputChannel, adapter, properties) { + DefaultBinding consumerBinding = new DefaultBinding(bindingName, group, moduleInputChannel, adapter) { @Override protected void afterUnbind() { @@ -207,7 +183,7 @@ public class RedisMessageChannelBinder extends AbstractBinder { * @return The channel, or a wrapper. */ private MessageChannel addRetryIfNeeded(final String name, final DirectChannel bridgeToModuleChannel, - RedisPropertiesAccessor properties) { + ConsumerProperties properties) { final RetryTemplate retryTemplate = buildRetryTemplateIfRetryEnabled(properties); if (retryTemplate == null) { return bridgeToModuleChannel; @@ -256,18 +232,14 @@ public class RedisMessageChannelBinder extends AbstractBinder { } @Override - public Binding bindProducer(final String name, MessageChannel moduleOutputChannel, Properties properties) { + protected Binding doBindProducer(final String name, MessageChannel moduleOutputChannel, ProducerProperties properties) { Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel); - validateProducerProperties(name, properties, SUPPORTED_PRODUCER_PROPERTIES); - RedisPropertiesAccessor accessor = new RedisPropertiesAccessor(properties); - return doRegisterProducer(name, moduleOutputChannel, accessor); + return doRegisterProducer(name, moduleOutputChannel, properties); } - private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, RedisPropertiesAccessor accessor) { - String partitionKeyExtractorClass = accessor.getPartitionKeyExtractorClass(); - Expression partitionKeyExpression = accessor.getPartitionKeyExpression(); + private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, ProducerProperties properties) { RedisQueueOutboundChannelAdapter queue; - if (partitionKeyExpression == null && !StringUtils.hasText(partitionKeyExtractorClass)) { + if (!properties.isPartitioned()) { queue = new RedisQueueOutboundChannelAdapter(name, this.connectionFactory); } else { @@ -280,15 +252,17 @@ public class RedisMessageChannelBinder extends AbstractBinder { return queue; } - private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel, RedisPropertiesAccessor properties) { + private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel, + ProducerProperties properties) { Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel); MessageHandler handler = new SendingHandler(name, properties); EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler); consumer.setBeanFactory(this.getBeanFactory()); consumer.setBeanName("outbound." + name); consumer.afterPropertiesSet(); - DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer, properties); - String[] requiredGroups = properties.getRequiredGroups(defaultRequiredGroups); + DefaultBinding producerBinding = + new DefaultBinding<>(name, null, moduleOutputChannel, consumer); + String[] requiredGroups = properties.getRequiredGroups(); if (!ObjectUtils.isEmpty(requiredGroups)) { for (String group : requiredGroups) { this.redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + name).incrementScore(group, 1); @@ -302,19 +276,18 @@ public class RedisMessageChannelBinder extends AbstractBinder { private final String bindingName; - private final RedisPropertiesAccessor accessor; + private final ProducerProperties producerProperties; private final Map adapters = new HashMap<>(); private final PartitionHandler partitionHandler; - private SendingHandler(String bindingName, RedisPropertiesAccessor properties) { + private SendingHandler(String bindingName, ProducerProperties producerProperties) { this.bindingName = bindingName; - this.accessor = properties; + this.producerProperties = producerProperties; ConfigurableListableBeanFactory beanFactory = RedisMessageChannelBinder.this.getBeanFactory(); this.setBeanFactory(beanFactory); - this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, - properties, properties.getNextModuleCount()); + this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, producerProperties); refreshChannelAdapters(); } @@ -322,7 +295,7 @@ public class RedisMessageChannelBinder extends AbstractBinder { protected void handleMessageInternal(Message message) throws Exception { MessageValues transformed = serializePayloadIfNecessary(message); - if (this.partitionHandler.isPartitionedModule()) { + if (producerProperties.isPartitioned()) { transformed.put(PARTITION_HEADER, this.partitionHandler.determinePartition(message)); } @@ -340,7 +313,7 @@ public class RedisMessageChannelBinder extends AbstractBinder { for (String group : groups) { if (!adapters.containsKey(group)) { String channel = String.format("%s.%s", this.bindingName, group); - adapters.put(group, createProducerEndpoint(channel, accessor)); + adapters.put(group, createProducerEndpoint(channel, producerProperties)); } } } @@ -375,14 +348,6 @@ public class RedisMessageChannelBinder extends AbstractBinder { } - private static class RedisPropertiesAccessor extends DefaultBindingPropertiesAccessor { - - public RedisPropertiesAccessor(Properties properties) { - super(properties); - } - - } - /** * Provides concurrency by creating a list of message-driven endpoints. */ diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisMessageChannelBinderConfiguration.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisMessageChannelBinderConfiguration.java index e5475c242..4410ebe0d 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisMessageChannelBinderConfiguration.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisMessageChannelBinderConfiguration.java @@ -19,7 +19,6 @@ package org.springframework.cloud.stream.binder.redis.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.stream.binder.redis.RedisMessageChannelBinder; import org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -32,7 +31,6 @@ import org.springframework.integration.codec.Codec; * @author David Turanski */ @Configuration -@EnableConfigurationProperties(RedisBinderConfigurationProperties.class) @Import({PropertyPlaceholderAutoConfiguration.class, KryoCodecAutoConfiguration.class}) @ConfigurationProperties(prefix = "spring.cloud.stream.binder.redis") public class RedisMessageChannelBinderConfiguration { @@ -42,8 +40,6 @@ public class RedisMessageChannelBinderConfiguration { @Autowired private Codec codec; - @Autowired - private RedisBinderConfigurationProperties redisBinderConfigurationProperties; @Autowired private RedisConnectionFactory redisConnectionFactory; @@ -54,11 +50,6 @@ public class RedisMessageChannelBinderConfiguration { RedisMessageChannelBinder redisMessageChannelBinder = new RedisMessageChannelBinder(this.redisConnectionFactory, this.headers); redisMessageChannelBinder.setCodec(this.codec); - redisMessageChannelBinder.setDefaultBackOffInitialInterval(this.redisBinderConfigurationProperties.getBackOffInitialInterval()); - redisMessageChannelBinder.setDefaultBackOffMaxInterval(this.redisBinderConfigurationProperties.getBackOffMaxInterval()); - redisMessageChannelBinder.setDefaultBackOffMultiplier(this.redisBinderConfigurationProperties.getBackOffMultiplier()); - redisMessageChannelBinder.setDefaultConcurrency(this.redisBinderConfigurationProperties.getConcurrency()); - redisMessageChannelBinder.setDefaultMaxAttempts(this.redisBinderConfigurationProperties.getMaxAttempts()); return redisMessageChannelBinder; } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisBinderTests.java index 547676c8f..5d8f331e8 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisBinderTests.java @@ -30,17 +30,16 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; -import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter; import org.springframework.cloud.stream.binder.PartitionCapableBinderTests; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.binder.Spy; import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -61,7 +60,7 @@ import org.springframework.retry.support.RetryTemplate; * @author David Turanski * @author Mark Fisher */ -public class RedisBinderTests extends PartitionCapableBinderTests { +public class RedisBinderTests extends PartitionCapableBinderTests { private final String CLASS_UNDER_TEST_NAME = RedisMessageChannelBinder.class.getSimpleName(); @@ -74,13 +73,23 @@ public class RedisBinderTests extends PartitionCapableBinderTests { new EmbeddedHeadersMessageConverter(); @Override - protected Binder getBinder() { + protected RedisTestBinder getBinder() { if (testBinder == null) { testBinder = new RedisTestBinder(redisAvailableRule.getResource()); } return testBinder; } + @Override + protected ConsumerProperties createConsumerProperties() { + return new ConsumerProperties(); + } + + @Override + protected ProducerProperties createProducerProperties() { + return new ProducerProperties(); + } + @Override protected boolean usesExplicitRouting() { return true; @@ -88,24 +97,25 @@ public class RedisBinderTests extends PartitionCapableBinderTests { @Test public void testConsumerProperties() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("maxAttempts", "1"); // disable retry - Binding binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties); + RedisTestBinder binder = getBinder(); + ConsumerProperties properties1 = new ConsumerProperties(); + properties1.setMaxAttempts(1); + Binding binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties1); AbstractEndpoint endpoint = extractEndpoint(binding); assertThat(endpoint, instanceOf(RedisQueueMessageDrivenEndpoint.class)); assertSame(DirectChannel.class, TestUtils.getPropertyValue(endpoint, "outputChannel").getClass()); binding.unbind(); assertFalse(endpoint.isRunning()); - properties.put("backOffInitialInterval", "2000"); - properties.put("backOffMaxInterval", "20000"); - properties.put("backOffMultiplier", "5.0"); - properties.put("concurrency", "2"); - properties.put("maxAttempts", "23"); - properties.put("partitionIndex", 0); + ConsumerProperties properties2 = new ConsumerProperties(); + properties2.setBackOffInitialInterval(2000); + properties2.setBackOffMaxInterval(20000); + properties2.setBackOffMultiplier(5.0); + properties2.setConcurrency(2); + properties2.setMaxAttempts(23); + properties2.setInstanceIndex(0); - binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties); + binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties2); endpoint = extractEndpoint(binding); verifyConsumer(endpoint); @@ -115,9 +125,9 @@ public class RedisBinderTests extends PartitionCapableBinderTests { @Test public void testProducerProperties() throws Exception { - Binder binder = getBinder(); - Binding consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), null); - Binding producerBinding = binder.bindProducer("props.0", new DirectChannel(), null); + RedisTestBinder binder = getBinder(); + Binding consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), createConsumerProperties()); + Binding producerBinding = binder.bindProducer("props.0", new DirectChannel(), createProducerProperties()); AbstractEndpoint producerEndpoint = extractEndpoint(producerBinding); @SuppressWarnings("unchecked") Map adapters = TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class); @@ -128,14 +138,14 @@ public class RedisBinderTests extends PartitionCapableBinderTests { producerBinding.unbind(); assertFalse(producerEndpoint.isRunning()); - Properties properties = new Properties(); - properties.put("partitionKeyExpression", "'foo'"); - properties.put("partitionKeyExtractorClass", "foo"); - properties.put("partitionSelectorExpression", "0"); - properties.put("partitionSelectorClass", "foo"); - properties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "1"); + ProducerProperties producerProperties = new ProducerProperties(); + producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'")); + producerProperties.setPartitionKeyExtractorClass(AbstractRedisSerializerTests.Foo.class); + producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0")); + producerProperties.setPartitionSelectorClass(AbstractRedisSerializerTests.Foo.class); + producerProperties.setPartitionCount(1); - producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties); + producerBinding = binder.bindProducer("props.0", new DirectChannel(), producerProperties); producerEndpoint = extractEndpoint(producerBinding); adapter = (RedisQueueOutboundChannelAdapter) TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class).get("test"); assertEquals( @@ -168,15 +178,15 @@ public class RedisBinderTests extends PartitionCapableBinderTests { @Test public void testRetryFail() { - Binder binder = getBinder(); + RedisTestBinder binder = getBinder(); DirectChannel channel = new DirectChannel(); - binder.bindProducer("retry.0", channel, null); - Properties props = new Properties(); - props.put("maxAttempts", 2); - props.put("backOffInitialInterval", 100); - props.put("backOffMultiplier", "1.0"); - Binding consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), props); // no subscriber - channel.send(new GenericMessage("foo")); + binder.bindProducer("retry.0", channel, createProducerProperties()); + ConsumerProperties consumerProperties = new ConsumerProperties(); + consumerProperties.setMaxAttempts(2); + consumerProperties.setBackOffInitialInterval(100); + consumerProperties.setBackOffMultiplier(1.0); + Binding consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), consumerProperties); // no subscriber + channel.send(new GenericMessage<>("foo")); RedisTemplate template = createTemplate(); Object rightPop = template.boundListOps("ERRORS:retry.0.test").rightPop(5, TimeUnit.SECONDS); assertNotNull(rightPop); diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisTestBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisTestBinder.java index d3fc05276..98233b5bd 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisTestBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/RedisTestBinder.java @@ -17,6 +17,8 @@ package org.springframework.cloud.stream.binder.redis; import org.springframework.cloud.stream.binder.AbstractTestBinder; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.context.support.GenericApplicationContext; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; @@ -34,7 +36,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; * @author Gary Russell * @author David Turanski */ -public class RedisTestBinder extends AbstractTestBinder { +public class RedisTestBinder extends AbstractTestBinder { private StringRedisTemplate template; diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/integration/RedisBinderModuleTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/integration/RedisBinderModuleTests.java index e8104eb59..8bed037f3 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/integration/RedisBinderModuleTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/test/java/org/springframework/cloud/stream/binder/redis/integration/RedisBinderModuleTests.java @@ -74,7 +74,7 @@ public class RedisBinderModuleTests { public void testParentConnectionFactoryInheritedByDefault() { context = SpringApplication.run(SimpleProcessor.class, "--server.port=0"); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RedisMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); RedisConnectionFactory binderConnectionFactory = @@ -97,7 +97,7 @@ public class RedisBinderModuleTests { public void testParentConnectionFactoryInheritedIfOverridden() { context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run(); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RedisMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); RedisConnectionFactory binderConnectionFactory = @@ -125,7 +125,7 @@ public class RedisBinderModuleTests { params.add("--spring.cloud.stream.binders.custom.environment.foo=bar"); context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[]{})); BinderFactory binderFactory = context.getBean(BinderFactory.class); - Binder binder = binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); assertThat(binder, instanceOf(RedisMessageChannelBinder.class)); DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder); RedisConnectionFactory binderConnectionFactory = diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java index ea32adddd..1ba955837 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java @@ -25,14 +25,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; -import java.util.Collection; -import java.util.Collections; import java.util.UUID; import org.junit.After; import org.junit.Test; -import org.springframework.http.MediaType; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.endpoint.AbstractEndpoint; @@ -49,11 +46,9 @@ import org.springframework.util.MimeTypeUtils; * @author David Turanski * @author Mark Fisher */ -public abstract class AbstractBinderTests { +public abstract class AbstractBinderTests, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> { - protected static final Collection ALL = Collections.singletonList(MediaType.ALL); - - protected AbstractTestBinder testBinder; + protected B testBinder; /** * Subclasses may override this default value to have tests wait longer for a message receive, for example if @@ -72,12 +67,12 @@ public abstract class AbstractBinderTests { @Test public void testClean() throws Exception { - Binder binder = getBinder(); - Binding foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), null); - Binding foo0ConsumerBinding = binder.bindConsumer("foo.0", "test", new DirectChannel(), null); - Binding foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), null); - Binding foo1ConsumerBinding = binder.bindConsumer("foo.1", "test", new DirectChannel(), null); - Binding foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), null); + Binder binder = getBinder(); + Binding foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), createProducerProperties()); + Binding foo0ConsumerBinding = binder.bindConsumer("foo.0", "test", new DirectChannel(), createConsumerProperties()); + Binding foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), createProducerProperties()); + Binding foo1ConsumerBinding = binder.bindConsumer("foo.1", "test", new DirectChannel(), createConsumerProperties()); + Binding foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), createProducerProperties()); foo0ProducerBinding.unbind(); assertFalse(TestUtils.getPropertyValue(foo0ProducerBinding, "endpoint", AbstractEndpoint.class).isRunning()); foo0ConsumerBinding.unbind(); @@ -92,11 +87,11 @@ public abstract class AbstractBinderTests { @Test public void testSendAndReceive() throws Exception { - Binder binder = getBinder(); + Binder binder = getBinder(); DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, null); - Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, null); + Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, createProducerProperties()); + Binding consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, createConsumerProperties()); Message message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar").build(); // Let the consumer actually bind to the producer before sending a msg @@ -113,18 +108,18 @@ public abstract class AbstractBinderTests { @Test public void testSendAndReceiveMultipleTopics() throws Exception { - Binder binder = getBinder(); + Binder binder = getBinder(); DirectChannel moduleOutputChannel1 = new DirectChannel(); DirectChannel moduleOutputChannel2 = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Binding producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, null); - Binding producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, null); + Binding producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, createProducerProperties()); + Binding producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, createProducerProperties()); - Binding consumerBinding1 = binder.bindConsumer("foo.x", "test", moduleInputChannel, null); - Binding consumerBinding2 = binder.bindConsumer("foo.y", "test", moduleInputChannel, null); + Binding consumerBinding1 = binder.bindConsumer("foo.x", "test", moduleInputChannel, createConsumerProperties()); + Binding consumerBinding2 = binder.bindConsumer("foo.y", "test", moduleInputChannel, createConsumerProperties()); String testPayload1 = "foo" + UUID.randomUUID().toString(); Message message1 = MessageBuilder.withPayload(testPayload1.getBytes()).build(); @@ -157,11 +152,12 @@ public abstract class AbstractBinderTests { @Test public void testSendAndReceiveNoOriginalContentType() throws Exception { - Binder binder = getBinder(); + Binder binder = getBinder(); + DirectChannel moduleOutputChannel = new DirectChannel(); QueueChannel moduleInputChannel = new QueueChannel(); - Binding producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, null); - Binding consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, null); + Binding producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, createProducerProperties()); + Binding consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, createConsumerProperties()); binderBindUnbindLatency(); Message message = MessageBuilder.withPayload("foo").build(); @@ -176,7 +172,11 @@ public abstract class AbstractBinderTests { } - protected abstract Binder getBinder() throws Exception; + protected abstract B getBinder() throws Exception; + + protected abstract CP createConsumerProperties(); + + protected abstract PP createProducerProperties(); @After public void cleanup() { diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractTestBinder.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractTestBinder.java index b58b35a99..2839d47d7 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractTestBinder.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractTestBinder.java @@ -17,7 +17,6 @@ package org.springframework.cloud.stream.binder; import java.util.HashSet; -import java.util.Properties; import java.util.Set; import org.springframework.messaging.MessageChannel; @@ -29,7 +28,7 @@ import org.springframework.messaging.MessageChannel; * @author Gary Russell * @author Mark Fisher */ -public abstract class AbstractTestBinder implements Binder { +public abstract class AbstractTestBinder, CP extends ConsumerProperties, PP extends ProducerProperties> implements Binder { protected Set queues = new HashSet(); @@ -46,13 +45,13 @@ public abstract class AbstractTestBinder implements Bi } @Override - public Binding bindConsumer(String name, String group, MessageChannel moduleInputChannel, Properties properties) { + public Binding bindConsumer(String name, String group, MessageChannel moduleInputChannel, CP properties) { queues.add(name); return binder.bindConsumer(name, group, moduleInputChannel, properties); } @Override - public Binding bindProducer(String name, MessageChannel moduleOutputChannel, Properties properties) { + public Binding bindProducer(String name, MessageChannel moduleOutputChannel, PP properties) { queues.add(name); return binder.bindProducer(name, moduleOutputChannel, properties); } diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/BrokerBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/BrokerBinderTests.java index 59f9a926f..ba3c7d239 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/BrokerBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/BrokerBinderTests.java @@ -16,12 +16,14 @@ package org.springframework.cloud.stream.binder; +import org.springframework.messaging.MessageChannel; + /** * Tests for binders that use an external broker. * * @author Gary Russell */ -public abstract class BrokerBinderTests extends AbstractBinderTests { +public abstract class BrokerBinderTests, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> extends AbstractBinderTests { /** * Create a new spy on the given 'queue'. This allows de-correlating the creation of diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/PartitionCapableBinderTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/PartitionCapableBinderTests.java index 5a080d729..9fb57e9f4 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/PartitionCapableBinderTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/PartitionCapableBinderTests.java @@ -16,7 +16,6 @@ package org.springframework.cloud.stream.binder; -import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -28,15 +27,14 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import java.util.Arrays; -import java.util.Properties; import java.util.UUID; import org.hamcrest.CustomMatcher; import org.hamcrest.Matcher; -import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; @@ -53,21 +51,22 @@ import org.springframework.messaging.support.GenericMessage; * @author Mark Fisher * @author Marius Bogoevici */ -abstract public class PartitionCapableBinderTests extends BrokerBinderTests { +abstract public class PartitionCapableBinderTests, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> extends BrokerBinderTests { + + protected static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); @Test @SuppressWarnings("unchecked") public void testAnonymousGroup() throws Exception { - Binder binder = getBinder(); + B binder = getBinder(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); - Binding producerBinding = binder.bindProducer("defaultGroup.0", output, properties); + Binding producerBinding = binder.bindProducer("defaultGroup.0", output, createProducerProperties()); QueueChannel input1 = new QueueChannel(); - Binding binding1 = binder.bindConsumer("defaultGroup.0", null, input1, properties); + Binding binding1 = binder.bindConsumer("defaultGroup.0", null, input1, createConsumerProperties()); QueueChannel input2 = new QueueChannel(); - Binding binding2 = binder.bindConsumer("defaultGroup.0", null, input2, properties); + Binding binding2 = binder.bindConsumer("defaultGroup.0", null, input2, createConsumerProperties()); String testPayload1 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload1.getBytes())); @@ -85,7 +84,7 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests { String testPayload2 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload2.getBytes())); - binding2 = binder.bindConsumer("defaultGroup.0", null, input2, properties); + binding2 = binder.bindConsumer("defaultGroup.0", null, input2, createConsumerProperties()); String testPayload3 = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload3.getBytes())); @@ -107,21 +106,21 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests { @Test public void testOneRequiredGroup() throws Exception { - Binder binder = getBinder(); + B binder = getBinder(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); + + PP producerProperties = createProducerProperties(); String testDestination = "testDestination" + UUID.randomUUID().toString().replace("-", ""); - properties.put("requiredGroups", "test1"); - Binding producerBinding = binder.bindProducer(testDestination, output, properties); + producerProperties.setRequiredGroups("test1"); + Binding producerBinding = binder.bindProducer(testDestination, output, producerProperties); String testPayload = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload.getBytes())); - properties.clear(); QueueChannel inbound1 = new QueueChannel(); - Binding consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1, properties); + Binding consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1, createConsumerProperties()); Message receivedMessage1 = receive(inbound1); assertThat(receivedMessage1, not(nullValue())); @@ -133,23 +132,22 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests { @Test public void testTwoRequiredGroups() throws Exception { - Binder binder = getBinder(); + B binder = getBinder(); DirectChannel output = new DirectChannel(); - Properties properties = new Properties(); String testDestination = "testDestination" + UUID.randomUUID().toString().replace("-", ""); - properties.put("requiredGroups", "test1,test2"); - Binding producerBinding = binder.bindProducer(testDestination, output, properties); + PP producerProperties = createProducerProperties(); + producerProperties.setRequiredGroups("test1","test2"); + Binding producerBinding = binder.bindProducer(testDestination, output, producerProperties); String testPayload = "foo-" + UUID.randomUUID().toString(); output.send(new GenericMessage<>(testPayload.getBytes())); - properties.clear(); QueueChannel inbound1 = new QueueChannel(); - Binding consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1, properties); + Binding consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1, createConsumerProperties()); QueueChannel inbound2 = new QueueChannel(); - Binding consumerBinding2 = binder.bindConsumer(testDestination, "test2", inbound2, properties); + Binding consumerBinding2 = binder.bindConsumer(testDestination, "test2", inbound2, createConsumerProperties()); Message receivedMessage1 = receive(inbound1); assertThat(receivedMessage1, not(nullValue())); @@ -163,60 +161,31 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests { producerBinding.unbind(); } - @Test - public void testBadProperties() throws Exception { - Binder binder = getBinder(); - Properties properties = new Properties(); - properties.put("foo", "bar"); - properties.put("baz", "qux"); - - DirectChannel output = new DirectChannel(); - try { - binder.bindProducer("badprops.0", output, properties); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage(), allOf(Matchers.containsString(getClassUnderTestName() - + " does not support producer "), - containsString("foo"), - containsString("baz"), - containsString(" for badprops.0"))); - } - - properties.remove("baz"); - try { - binder.bindConsumer("badprops.0", "test", output, properties); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage(), equalTo(getClassUnderTestName() - + " does not support consumer property: foo for badprops.0.test.")); - } - } - @Test public void testPartitionedModuleSpEL() throws Exception { - Binder binder = getBinder(); + B binder = getBinder(); - Properties consumerProperties = new Properties(); - consumerProperties.put("concurrency", "2"); - consumerProperties.put("partitionIndex", "0"); - consumerProperties.put("count","3"); + CP consumerProperties = createConsumerProperties(); + consumerProperties.setConcurrency(2); + consumerProperties.setInstanceIndex(0); + consumerProperties.setInstanceCount(3); + consumerProperties.setPartitioned(true); QueueChannel input0 = new QueueChannel(); input0.setBeanName("test.input0S"); Binding input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties); - consumerProperties.put("partitionIndex", "1"); + consumerProperties.setInstanceIndex(1); QueueChannel input1 = new QueueChannel(); input1.setBeanName("test.input1S"); Binding input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties); - consumerProperties.put("partitionIndex", "2"); + consumerProperties.setInstanceIndex(2); QueueChannel input2 = new QueueChannel(); input2.setBeanName("test.input2S"); Binding input2Binding = binder.bindConsumer("part.0", "test", input2, consumerProperties); - Properties producerProperties = new Properties(); - producerProperties.put("partitionKeyExpression", "payload"); - producerProperties.put("partitionSelectorExpression", "hashCode()"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2"); + PP producerProperties = createProducerProperties(); + producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload")); + producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()")); + producerProperties.setPartitionCount(3); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); @@ -288,29 +257,29 @@ abstract public class PartitionCapableBinderTests extends BrokerBinderTests { @Test public void testPartitionedModuleJava() throws Exception { - Binder binder = getBinder(); + B binder = getBinder(); - Properties consumerProperties = new Properties(); - consumerProperties.put("concurrency", "2"); - consumerProperties.put("count","3"); - consumerProperties.put("partitionIndex", "0"); + CP consumerProperties = createConsumerProperties(); + consumerProperties.setConcurrency(2); + consumerProperties.setInstanceCount(3); + consumerProperties.setInstanceIndex(0); + consumerProperties.setPartitioned(true); QueueChannel input0 = new QueueChannel(); input0.setBeanName("test.input0J"); Binding input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties); - consumerProperties.put("partitionIndex", "1"); + consumerProperties.setInstanceIndex(1); QueueChannel input1 = new QueueChannel(); input1.setBeanName("test.input1J"); Binding input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties); - consumerProperties.put("partitionIndex", "2"); + consumerProperties.setInstanceIndex(2); QueueChannel input2 = new QueueChannel(); input2.setBeanName("test.input2J"); Binding input2Binding = binder.bindConsumer("partJ.0", "test", input2, consumerProperties); - Properties producerProperties = new Properties(); - producerProperties.put("partitionKeyExtractorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - producerProperties.put("partitionSelectorClass", "org.springframework.cloud.stream.binder.PartitionTestSupport"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_COUNT, "3"); - producerProperties.put(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, "2"); + PP producerProperties = createProducerProperties(); + producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class); + producerProperties.setPartitionSelectorClass(PartitionTestSupport.class); + producerProperties.setPartitionCount(3); DirectChannel output = new DirectChannel(); output.setBeanName("test.output"); Binding outputBinding = binder.bindProducer("partJ.0", output, producerProperties); diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java index e835a89ce..c22fe9d5f 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java +++ b/spring-cloud-stream-binders/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertSame; import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.Properties; import org.junit.Before; import org.junit.Test; @@ -299,15 +298,15 @@ public class MessageChannelBinderSupportTests { } - public class TestMessageChannelBinder extends AbstractBinder { + public class TestMessageChannelBinder extends AbstractBinder { @Override - protected Binding doBindConsumer(String name, String group, MessageChannel channel, Properties properties) { + protected Binding doBindConsumer(String name, String group, MessageChannel channel, ConsumerProperties properties) { return null; } @Override - public Binding bindProducer(String name, MessageChannel channel, Properties properties) { + public Binding doBindProducer(String name, MessageChannel channel, ProducerProperties properties) { return null; } } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java index 0a4e5c593..afc525a66 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java @@ -83,7 +83,6 @@ public class MessageChannelConfigurerTests { String inputBindingProps = headerValue.get("input"); assertTrue(inputBindingProps.contains("destination=configure")); assertTrue(inputBindingProps.contains("trackHistory=true")); - assertTrue(inputBindingProps.contains("concurrency=1")); assertTrue(headerValue.get("instanceIndex").equals("0")); assertTrue(headerValue.get("instanceCount").equals("1")); latch.countDown(); diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinder.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinder.java index 70567de9a..e884a3ea1 100644 --- a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinder.java +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinder.java @@ -18,7 +18,6 @@ package org.springframework.cloud.stream.test.binder; import java.util.HashMap; import java.util.Map; -import java.util.Properties; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -26,6 +25,8 @@ import java.util.concurrent.LinkedBlockingDeque; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.test.matcher.MessageQueueMatcher; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -45,14 +46,14 @@ import org.springframework.util.Assert; * @author Mark Fisher * @see MessageQueueMatcher */ -public class TestSupportBinder implements Binder { +public class TestSupportBinder implements Binder { private final MessageCollectorImpl messageCollector = new MessageCollectorImpl(); private final ConcurrentMap messageChannels = new ConcurrentHashMap<>(); @Override - public Binding bindConsumer(String name, String group, MessageChannel inboundBindTarget, Properties properties) { + public Binding bindConsumer(String name, String group, MessageChannel inboundBindTarget, ConsumerProperties properties) { return new TestBinding(inboundBindTarget, null); } @@ -60,7 +61,7 @@ public class TestSupportBinder implements Binder { * Registers a single subscriber to the channel, that enqueues messages for later retrieval and assertion in tests. */ @Override - public Binding bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) { + public Binding bindProducer(String name, MessageChannel outboundBindTarget, ProducerProperties properties) { final BlockingQueue> queue = messageCollector.register(outboundBindTarget); ((SubscribableChannel)outboundBindTarget).subscribe(new MessageHandler() { @Override diff --git a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java index 617c28f77..897620912 100644 --- a/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java +++ b/spring-cloud-stream-test-support/src/main/java/org/springframework/cloud/stream/test/binder/TestSupportBinderAutoConfiguration.java @@ -39,7 +39,7 @@ import org.springframework.messaging.MessageChannel; @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class TestSupportBinderAutoConfiguration { - private Binder messageChannelBinder = new TestSupportBinder(); + private Binder messageChannelBinder = new TestSupportBinder(); @Bean public BinderFactory binderFactory() { diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java index c135f4169..5b95a311e 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/EnableBinding.java @@ -26,6 +26,7 @@ import java.lang.annotation.Target; import org.springframework.cloud.stream.config.BinderFactoryConfiguration; import org.springframework.cloud.stream.config.BindingBeansRegistrar; import org.springframework.cloud.stream.config.ChannelBindingServiceConfiguration; +import org.springframework.cloud.stream.config.SpelExpressionConverterConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.integration.config.EnableIntegration; @@ -43,7 +44,8 @@ import org.springframework.integration.config.EnableIntegration; @Documented @Inherited @Configuration -@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class}) +@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class, + SpelExpressionConverterConfiguration.class}) @EnableIntegration public @interface EnableBinding { diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java index ba84a2636..91cd907eb 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java @@ -22,13 +22,8 @@ import static org.springframework.util.MimeTypeUtils.APPLICATION_OCTET_STREAM; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.HashSet; import java.util.LinkedList; import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -50,10 +45,8 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; -import org.springframework.util.AlternativeJdkIdGenerator; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.IdGenerator; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.StringUtils; @@ -65,7 +58,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Marius Bogoevici */ -public abstract class AbstractBinder implements ApplicationContextAware, InitializingBean, Binder { +public abstract class AbstractBinder implements ApplicationContextAware, InitializingBean, Binder { protected static final String PARTITION_HEADER = "partition"; @@ -82,97 +75,10 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init private final StringConvertingContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver(); - private static final int DEFAULT_BACKOFF_INITIAL_INTERVAL = 1000; - - private static final int DEFAULT_BACKOFF_MAX_INTERVAL = 10000; - - private static final double DEFAULT_BACKOFF_MULTIPLIER = 2.0; - - private static final int DEFAULT_CONCURRENCY = 1; - - private static final int DEFAULT_MAX_ATTEMPTS = 3; - - private static final int DEFAULT_BATCH_SIZE = 50; - - private static final int DEFAULT_BATCH_BUFFER_LIMIT = 10000; - - private static final int DEFAULT_BATCH_TIMEOUT = 0; - - /** - * The set of properties every binder implementation must support (or at least tolerate). - */ - - protected static final Set CONSUMER_STANDARD_PROPERTIES = new SetBuilder() - .add(BinderPropertyKeys.COUNT) - .add(BinderPropertyKeys.SEQUENCE) - .build(); - - protected static final Set PRODUCER_STANDARD_PROPERTIES = new HashSet(Arrays.asList( - BinderPropertyKeys.NEXT_MODULE_COUNT, - BinderPropertyKeys.NEXT_MODULE_CONCURRENCY - )); - - - protected static final Set CONSUMER_RETRY_PROPERTIES = new HashSet(Arrays.asList(new String[] { - BinderPropertyKeys.BACK_OFF_INITIAL_INTERVAL, - BinderPropertyKeys.BACK_OFF_MAX_INTERVAL, - BinderPropertyKeys.BACK_OFF_MULTIPLIER, - BinderPropertyKeys.MAX_ATTEMPTS - })); - - protected static final Set PRODUCER_PARTITIONING_PROPERTIES = new HashSet( - Arrays.asList(new String[] { - BinderPropertyKeys.PARTITION_KEY_EXPRESSION, - BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS, - BinderPropertyKeys.PARTITION_SELECTOR_CLASS, - BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION, - BinderPropertyKeys.MIN_PARTITION_COUNT - })); - - protected static final Set PRODUCER_BATCHING_BASIC_PROPERTIES = new HashSet( - Arrays.asList(new String[] { - BinderPropertyKeys.BATCHING_ENABLED, - BinderPropertyKeys.BATCH_SIZE, - BinderPropertyKeys.BATCH_TIMEOUT, - })); - - protected static final Set PRODUCER_BATCHING_ADVANCED_PROPERTIES = new HashSet( - Arrays.asList(new String[] { - BinderPropertyKeys.BATCH_BUFFER_LIMIT, - })); - - private final IdGenerator idGenerator = new AlternativeJdkIdGenerator(); - protected volatile EvaluationContext evaluationContext; protected volatile PartitionSelectorStrategy partitionSelector; - protected volatile long defaultBackOffInitialInterval = DEFAULT_BACKOFF_INITIAL_INTERVAL; - - protected volatile long defaultBackOffMaxInterval = DEFAULT_BACKOFF_MAX_INTERVAL; - - protected volatile double defaultBackOffMultiplier = DEFAULT_BACKOFF_MULTIPLIER; - - protected volatile int defaultConcurrency = DEFAULT_CONCURRENCY; - - protected volatile int defaultMaxAttempts = DEFAULT_MAX_ATTEMPTS; - - // properties for binder implementations that support batching - - protected volatile boolean defaultBatchingEnabled = false; - - protected volatile int defaultBatchSize = DEFAULT_BATCH_SIZE; - - protected volatile int defaultBatchBufferLimit = DEFAULT_BATCH_BUFFER_LIMIT; - - protected volatile long defaultBatchTimeout = DEFAULT_BATCH_TIMEOUT; - - protected volatile String[] defaultRequiredGroups = new String[] {}; - - // compression - - protected volatile boolean defaultCompress = false; - // Payload type cache private volatile Map> payloadTypeCache = new ConcurrentHashMap<>(); @@ -212,110 +118,10 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init this.codec = codec; } - protected IdGenerator getIdGenerator() { - return this.idGenerator; - } - public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; } - /** - * Set the partition strategy to be used by this binder if no partitionExpression is provided for a module. - * @param partitionSelector The selector. - */ - public void setPartitionSelector(PartitionSelectorStrategy partitionSelector) { - this.partitionSelector = partitionSelector; - } - - /** - * Set the default retry back off initial interval for this binder; can be overridden with consumer - * 'backOffInitialInterval' property. - * @param defaultBackOffInitialInterval - */ - public void setDefaultBackOffInitialInterval(long defaultBackOffInitialInterval) { - this.defaultBackOffInitialInterval = defaultBackOffInitialInterval; - } - - /** - * Set the default retry back off multiplier for this binder; can be overridden with consumer 'backOffMultiplier' - * property. - * @param defaultBackOffMultiplier - */ - public void setDefaultBackOffMultiplier(double defaultBackOffMultiplier) { - this.defaultBackOffMultiplier = defaultBackOffMultiplier; - } - - /** - * Set the default retry back off max interval for this binder; can be overridden with consumer - * 'backOffMaxInterval' - * property. - * @param defaultBackOffMaxInterval - */ - public void setDefaultBackOffMaxInterval(long defaultBackOffMaxInterval) { - this.defaultBackOffMaxInterval = defaultBackOffMaxInterval; - } - - /** - * Set the default concurrency for this binder; can be overridden with consumer 'concurrency' property. - * @param defaultConcurrency - */ - public void setDefaultConcurrency(int defaultConcurrency) { - this.defaultConcurrency = defaultConcurrency; - } - - /** - * The default maximum delivery attempts for this binder. Can be overridden by consumer property 'maxAttempts' if - * supported. Values less than 2 disable retry and one delivery attempt is made. - * @param defaultMaxAttempts The default maximum attempts. - */ - public void setDefaultMaxAttempts(int defaultMaxAttempts) { - this.defaultMaxAttempts = defaultMaxAttempts; - } - - /** - * Set whether this binder batches message sends by default. Only applies to binder implementations that support - * batching. - * @param defaultBatchingEnabled the defaultBatchingEnabled to set. - */ - public void setDefaultBatchingEnabled(boolean defaultBatchingEnabled) { - this.defaultBatchingEnabled = defaultBatchingEnabled; - } - - /** - * Set the default batch size; only applies when batching is enabled and the binder supports batching. - * @param defaultBatchSize the defaultBatchSize to set. - */ - public void setDefaultBatchSize(int defaultBatchSize) { - this.defaultBatchSize = defaultBatchSize; - } - - /** - * Set the default batch buffer limit - used to send a batch early if its size exceeds this. Only applies if - * batching is enabled and the binder supports this property. - * @param defaultBatchBufferLimit the defaultBatchBufferLimit to set. - */ - public void setDefaultBatchBufferLimit(int defaultBatchBufferLimit) { - this.defaultBatchBufferLimit = defaultBatchBufferLimit; - } - - /** - * Set the default batch timeout - used to send a batch if no messages arrive during this time. Only applies if - * batching is enabled and the binder supports this property. - * @param defaultBatchTimeout the defaultBatchTimeout to set. - */ - public void setDefaultBatchTimeout(long defaultBatchTimeout) { - this.defaultBatchTimeout = defaultBatchTimeout; - } - - /** - * Set whether compression will be used by producers, by default. - * @param defaultCompress 'true' to use compression. - */ - public void setDefaultCompress(boolean defaultCompress) { - this.defaultCompress = defaultCompress; - } - @Override public final void afterPropertiesSet() throws Exception { Assert.notNull(this.applicationContext, "The 'applicationContext' property must not be null"); @@ -334,16 +140,22 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init } @Override - public final Binding bindConsumer(String name, String group, T target, Properties properties) { - DefaultBindingPropertiesAccessor accessor = new DefaultBindingPropertiesAccessor(properties); + public final Binding bindConsumer(String name, String group, T target, C properties) { if (StringUtils.isEmpty(group)) { - Assert.isTrue(accessor.getPartitionIndex() < 0, + Assert.isTrue(!properties.isPartitioned(), "A consumer group is required for a partitioned subscription"); } return doBindConsumer(name, group, target, properties); } - protected abstract Binding doBindConsumer(String name, String group, T inputTarget, Properties properties); + protected abstract Binding doBindConsumer(String name, String group, T inputTarget, C properties); + + @Override + public final Binding bindProducer(String name, T outboundBindTarget, P properties) { + return doBindProducer(name, outboundBindTarget, properties); + } + + protected abstract Binding doBindProducer(String name, T outboundBindTarget, P properties); /** * Construct a name comprised of the name and group. @@ -458,57 +270,6 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init } } - /** - * Validate the provided deployment properties for the consumer against those supported by this binder - * implementation. - * The consumer is that part of the binder that consumes messages from the underlying infrastructure and sends them - * to - * the next module. Consumer properties are used to configure the consumer. - * @param name The name. - * @param properties The properties. - * @param supported The supported properties. - */ - protected void validateConsumerProperties(String name, Properties properties, Set supported) { - if (properties != null) { - validateProperties(name, properties, supported, "consumer"); - } - } - - /** - * Validate the provided deployment properties for the producer against those supported by this binder - * implementation. - * When a module sends a message to the binder, the producer uses these properties while sending it to the - * underlying - * infrastructure. - * @param name The name. - * @param properties The properties. - * @param supported The supported properties. - */ - protected void validateProducerProperties(String name, Properties properties, Set supported) { - if (properties != null) { - validateProperties(name, properties, supported, "producer"); - } - } - - private void validateProperties(String name, Properties properties, Set supported, String type) { - StringBuilder builder = new StringBuilder(); - int errors = 0; - for (Entry entry : properties.entrySet()) { - if (!supported.contains(entry.getKey())) { - builder.append(entry.getKey()).append(","); - errors++; - } - } - if (errors > 0) { - throw new IllegalArgumentException(getClass().getSimpleName() + " does not support " - + type - + " propert" - + (errors == 1 ? "y: " : "ies: ") - + builder.substring(0, builder.length() - 1) - + " for " + name + "."); - } - } - protected String buildPartitionRoutingExpression(String expressionRoot) { return "'" + expressionRoot + "-' + headers['" + PARTITION_HEADER + "']"; } @@ -518,16 +279,16 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init * @param properties The properties. * @return The retry template, or null if retry is not enabled. */ - protected RetryTemplate buildRetryTemplateIfRetryEnabled(DefaultBindingPropertiesAccessor properties) { - int maxAttempts = properties.getMaxAttempts(this.defaultMaxAttempts); + protected RetryTemplate buildRetryTemplateIfRetryEnabled(ConsumerProperties properties) { + int maxAttempts = properties.getMaxAttempts(); if (maxAttempts > 1) { RetryTemplate template = new RetryTemplate(); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(maxAttempts); ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); - backOffPolicy.setInitialInterval(properties.getBackOffInitialInterval(this.defaultBackOffInitialInterval)); - backOffPolicy.setMultiplier(properties.getBackOffMultiplier(this.defaultBackOffMultiplier)); - backOffPolicy.setMaxInterval(properties.getBackOffMaxInterval(this.defaultBackOffMaxInterval)); + backOffPolicy.setInitialInterval(properties.getBackOffInitialInterval()); + backOffPolicy.setMultiplier(properties.getBackOffMultiplier()); + backOffPolicy.setMaxInterval(properties.getBackOffMaxInterval()); template.setRetryPolicy(retryPolicy); template.setBackOffPolicy(backOffPolicy); return template; @@ -593,26 +354,6 @@ public abstract class AbstractBinder implements ApplicationContextAware, Init } - public static class SetBuilder { - - private final Set set = new HashSet(); - - public SetBuilder add(Object o) { - this.set.add(o); - return this; - } - - public SetBuilder addAll(Set set) { - this.set.addAll(set); - return this; - } - - public Set build() { - return this.set; - } - - } - /** * Perform manual acknowledgement based on the metadata stored in the binder. */ diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/Binder.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/Binder.java index 7ded65508..3e86fd784 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/Binder.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/Binder.java @@ -16,8 +16,6 @@ package org.springframework.cloud.stream.binder; -import java.util.Properties; - /** * A strategy interface used to bind an app interface to a logical name. The name is intended to identify a * logical consumer or producer of messages. This may be a queue, a channel adapter, another message channel, a Spring @@ -28,9 +26,10 @@ import java.util.Properties; * @author Gary Russell * @author Jennifer Hickey * @author Ilayaperumal Gopinathan + * @author Marius Bogoevici * @since 1.0 */ -public interface Binder { +public interface Binder { /** * Bind the target component as a message consumer to the logical entity identified by the name. @@ -39,16 +38,16 @@ public interface Binder { * in the same group (a null or empty String, must be treated as an anonymous group that doesn't share * the subscription with any other consumer) * @param inboundBindTarget the app interface to be bound as a consumer - * @param properties arbitrary String key/value pairs that will be used as consumer properties in the binding + * @param consumerProperties the consumer properties */ - Binding bindConsumer(String name, String group, T inboundBindTarget, Properties properties); + Binding bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties); /** * Bind the target component as a message producer to the logical entity identified by the name. * @param name the logical identity of the message target * @param outboundBindTarget the app interface to be bound as a producer - * @param properties arbitrary String key/value pairs that will be used as producer properties in the binding + * @param producerProperties the producer properties */ - Binding bindProducer(String name, T outboundBindTarget, Properties properties); + Binding bindProducer(String name, T outboundBindTarget, P producerProperties); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java index b0bf33804..b86dc3d14 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderFactory.java @@ -28,5 +28,5 @@ public interface BinderFactory { * @param configurationName the name of a binder configuration * @return the binder instance */ - Binder getBinder(String configurationName); + Binder getBinder(String configurationName); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderPropertyKeys.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderPropertyKeys.java deleted file mode 100644 index 38cd19b59..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/BinderPropertyKeys.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2014-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binder; - -/** - * Common binder properties. - * - * @author Gary Russell - * @author Ilayaperumal Gopinathan - */ -public abstract class BinderPropertyKeys { - - /** - * The retry back off initial interval. - */ - public static final String BACK_OFF_INITIAL_INTERVAL = "backOffInitialInterval"; - - /** - * The retry back off max interval. - */ - public static final String BACK_OFF_MAX_INTERVAL = "backOffMaxInterval"; - - /** - * The retry back off multiplier. - */ - public static final String BACK_OFF_MULTIPLIER = "backOffMultiplier"; - - /** - * The minimum number of concurrent deliveries. - */ - public static final String CONCURRENCY = "concurrency"; - - /** - * The maximum delivery attempts when a delivery fails. - */ - public static final String MAX_ATTEMPTS = "maxAttempts"; - - /** - * The maximum number of concurrent deliveries. - */ - public static final String MAX_CONCURRENCY = "maxConcurrency"; - - /** - * The sequence index of the module. - * In a partitioned stream, it is identical to the partition index. - */ - public static final String SEQUENCE = "sequence"; - - /** - * The number of consumers, i.e. module instances in the stream. - * In a partitioned stream, it is identical to the partition count. - */ - public static final String COUNT = "count"; - - /** - * The consumer's partition index. - */ - public static final String PARTITION_INDEX = "partitionIndex"; - - /** - * The partition key expression. - */ - public static final String PARTITION_KEY_EXPRESSION = "partitionKeyExpression"; - - /** - * The partition key class. - */ - public static final String PARTITION_KEY_EXTRACTOR_CLASS = "partitionKeyExtractorClass"; - - /** - * The partition selector class. - */ - public static final String PARTITION_SELECTOR_CLASS = "partitionSelectorClass"; - - /** - * The partition selector expression. - */ - public static final String PARTITION_SELECTOR_EXPRESSION = "partitionSelectorExpression"; - - /** - * True if message batching is enabled. - */ - public static final String BATCHING_ENABLED = "batchingEnabled"; - - /** - * The batch size if batching is enabled. - */ - public static final String BATCH_SIZE = "batchSize"; - - /** - * The buffer limit if batching is enabled. - */ - public static final String BATCH_BUFFER_LIMIT = "batchBufferLimit"; - - /** - * The batch timeout if batching is enabled. - */ - public static final String BATCH_TIMEOUT = "batchTimeout"; - - /** - * For all non-terminal modules, the number of modules coming after this one, irrespective of partitioning. - */ - public static final String NEXT_MODULE_COUNT = "nextModuleCount"; - - /** - * For all non-terminal modules, the concurrency for module coming after this one. - */ - public static final String NEXT_MODULE_CONCURRENCY = "nextModuleConcurrency"; - - /** - * Compression enabled. - */ - public static final String COMPRESS = "compress"; - - /** - * Minimum partition count, if the transport supports partitioning natively (e.g. Kafka) - */ - public static final String MIN_PARTITION_COUNT = "minPartitionCount"; - - /** - * Required groups. The binder will ensure that consumers from these groups that bind after - * the producer will be able to receive messages produced in the mean time. - */ - public static final String REQUIRED_GROUPS = "requiredGroups"; - -} diff --git a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisBinderConfigurationProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java similarity index 60% rename from spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisBinderConfigurationProperties.java rename to spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java index 521ffb0c9..3eaae8be1 100644 --- a/spring-cloud-stream-binders/spring-cloud-stream-binder-redis/src/main/java/org/springframework/cloud/stream/binder/redis/config/RedisBinderConfigurationProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,45 +14,30 @@ * limitations under the License. */ -package org.springframework.cloud.stream.binder.redis.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; +package org.springframework.cloud.stream.binder; /** - * @author David Turanski + * Common consumer properties. + * + * @author Marius Bogoevici */ -@ConfigurationProperties(prefix = "spring.cloud.stream.binder.redis.default") -class RedisBinderConfigurationProperties { +public class ConsumerProperties { - private int backOffInitialInterval; - private int backOffMaxInterval; - private double backOffMultiplier; - private int concurrency; - private int maxAttempts; + private int concurrency = 1; - public int getBackOffInitialInterval() { - return backOffInitialInterval; - } + private boolean partitioned = false; - public void setBackOffInitialInterval(int backOffInitialInterval) { - this.backOffInitialInterval = backOffInitialInterval; - } + private int instanceCount = 1; - public int getBackOffMaxInterval() { - return backOffMaxInterval; - } + private int instanceIndex = 0; - public void setBackOffMaxInterval(int backOffMaxInterval) { - this.backOffMaxInterval = backOffMaxInterval; - } + private int maxAttempts = 3; - public double getBackOffMultiplier() { - return backOffMultiplier; - } + private int backOffInitialInterval = 1000; - public void setBackOffMultiplier(double backOffMultiplier) { - this.backOffMultiplier = backOffMultiplier; - } + private int backOffMaxInterval = 10000; + + private double backOffMultiplier = 2.0; public int getConcurrency() { return concurrency; @@ -62,11 +47,61 @@ class RedisBinderConfigurationProperties { this.concurrency = concurrency; } - public int getMaxAttempts() { - return maxAttempts; + public boolean isPartitioned() { + return partitioned; + } + + public void setPartitioned(boolean partitioned) { + this.partitioned = partitioned; + } + + public int getInstanceCount() { + return instanceCount; + } + + public void setInstanceCount(int instanceCount) { + this.instanceCount = instanceCount; + } + + public int getInstanceIndex() { + return instanceIndex; + } + + public void setInstanceIndex(int instanceIndex) { + this.instanceIndex = instanceIndex; } public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; } + + public int getMaxAttempts() { + return maxAttempts; + } + + public void setBackOffInitialInterval(int backOffInitialInterval) { + this.backOffInitialInterval = backOffInitialInterval; + } + + public int getBackOffInitialInterval() { + return backOffInitialInterval; + } + + public void setBackOffMaxInterval(int backOffMaxInterval) { + this.backOffMaxInterval = backOffMaxInterval; + } + + public int getBackOffMaxInterval() { + return backOffMaxInterval; + } + + public void setBackOffMultiplier(double backOffMultiplier) { + this.backOffMultiplier = backOffMultiplier; + } + + public double getBackOffMultiplier() { + return backOffMultiplier; + } + } + diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java index 96918f79f..c6f7a6171 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinderFactory.java @@ -87,7 +87,7 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean } @Override - public synchronized Binder getBinder(String name) { + public synchronized Binder getBinder(String name) { String configurationName; // Fall back to a default if no argument is provided if (StringUtils.isEmpty(name)) { @@ -155,7 +155,7 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean ConfigurableApplicationContext binderProducingContext = springApplicationBuilder.run(args.toArray(new String[args.size()])); @SuppressWarnings("unchecked") - Binder binder = binderProducingContext.getBean(Binder.class); + Binder binder = binderProducingContext.getBean(Binder.class); if (bindersHealthIndicator != null) { OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator(); Map indicators = binderProducingContext.getBeansOfType(HealthIndicator.class); @@ -177,16 +177,16 @@ public class DefaultBinderFactory implements BinderFactory, DisposableBean */ private static class BinderInstanceHolder { - private final Binder binderInstance; + private final Binder binderInstance; private final ConfigurableApplicationContext binderContext; - public BinderInstanceHolder(Binder binderInstance, ConfigurableApplicationContext binderContext) { + public BinderInstanceHolder(Binder binderInstance, ConfigurableApplicationContext binderContext) { this.binderInstance = binderInstance; this.binderContext = binderContext; } - public Binder getBinderInstance() { + public Binder getBinderInstance() { return this.binderInstance; } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinding.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinding.java index 7ba7f359a..d423a565e 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinding.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBinding.java @@ -39,17 +39,13 @@ public class DefaultBinding implements Binding { private final AbstractEndpoint endpoint; - private final DefaultBindingPropertiesAccessor properties; - - public DefaultBinding(String name, String group, T target, AbstractEndpoint endpoint, - DefaultBindingPropertiesAccessor properties) { + public DefaultBinding(String name, String group, T target, AbstractEndpoint endpoint) { Assert.notNull(target, "target must not be null"); Assert.notNull(endpoint, "endpoint must not be null"); this.name = name; this.group = group; this.target = target; this.endpoint = endpoint; - this.properties = properties; } @@ -71,10 +67,6 @@ public class DefaultBinding implements Binding { protected void afterUnbind() { } - public DefaultBindingPropertiesAccessor getPropertiesAccessor() { - return properties; - } - @Override public String toString() { return " Binding [name=" + name + ", target=" + target + ", endpoint=" + endpoint.getComponentName() diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBindingPropertiesAccessor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBindingPropertiesAccessor.java deleted file mode 100644 index 04001dadd..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/DefaultBindingPropertiesAccessor.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2014-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binder; - -import java.util.Properties; - -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.StringUtils; - - -/** - * Base class for binding-specific property accessors; common properties - * are defined here. - * - * @author Gary Russell - * @author Marius Bogoevici - */ -public class DefaultBindingPropertiesAccessor { - - private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); - - private final Properties properties; - - public DefaultBindingPropertiesAccessor(Properties properties) { - if (properties == null) { - this.properties = new Properties(); - } - else { - this.properties = properties; - } - } - - /** - * Return the underlying properties object. - * @return The properties. - */ - public Properties getProperties() { - return properties; - } - - /** - * Return the property for the key, or null if it doesn't exist. - * @param key The property. - * @return The key. - */ - public String getProperty(String key) { - return this.properties.getProperty(key); - } - - /** - * Return the property for the key, or the default value if the - * property doesn't exist. - * @param key The key. - * @param defaultValue The default value. - * @return The property or default value. - */ - public String getProperty(String key, String defaultValue) { - return this.properties.getProperty(key, defaultValue); - } - - /** - * Return the property for the key, or the default value if the - * property doesn't exist. - * @param key The key. - * @param defaultValue The default value. - * @return The property or default value. - */ - public boolean getProperty(String key, boolean defaultValue) { - String property = this.properties.getProperty(key); - if (property != null) { - return Boolean.parseBoolean(property); - } - else { - return defaultValue; - } - } - - /** - * Return the property for the key, or the default value if the - * property doesn't exist. - * @param key The key. - * @param defaultValue The default value. - * @return The property or default value. - */ - public int getProperty(String key, int defaultValue) { - String property = this.properties.getProperty(key); - if (property != null) { - return Integer.parseInt(property); - } - else { - return defaultValue; - } - } - - /** - * Return the property for the key, or the default value if the - * property doesn't exist. - * @param key The key. - * @param defaultValue The default value. - * @return The property or default value. - */ - public long getProperty(String key, long defaultValue) { - String property = this.properties.getProperty(key); - if (property != null) { - return Long.parseLong(property); - } - else { - return defaultValue; - } - } - - /** - * Return the property for the key, or the default value if the - * property doesn't exist. - * @param key The key. - * @param defaultValue The default value. - * @return The property or default value. - */ - public double getProperty(String key, double defaultValue) { - String property = properties.getProperty(key); - if (property != null) { - return Double.parseDouble(property); - } - else { - return defaultValue; - } - } - - /** - * Return the 'concurrency' property or the default value. - * The meaning of concurrency depends on the binder implementation. - * @param defaultValue The default value. - * @return The property or default value. - */ - public int getConcurrency(int defaultValue) { - return getProperty(BinderPropertyKeys.CONCURRENCY, defaultValue); - } - - /** - * Return the 'maxConcurrency' property or the default value. - * The meaning of maxConcurrency depends on the binder implementation. - * @param defaultValue The default value. - * @return The property or default value. - */ - public int getMaxConcurrency(int defaultValue) { - return getProperty(BinderPropertyKeys.MAX_CONCURRENCY, defaultValue); - } - - // Retry properties - - /** - * Return the 'maxAttempts' property or the default value. - * This is used in the retry template's SimpleRetryPolicy - * in binders that support retry. - * @param defaultValue The default value. - * @return The property or default value. - */ - public int getMaxAttempts(int defaultValue) { - return getProperty(BinderPropertyKeys.MAX_ATTEMPTS, defaultValue); - } - - /** - * Return the 'backOffInitialInterval' property or the default value. - * This is used in the retry template's ExponentialBackOffPolicy - * in binders that support retry. - * @param defaultValue The default value. - * @return The property or default value. - */ - public long getBackOffInitialInterval(long defaultValue) { - return getProperty(BinderPropertyKeys.BACK_OFF_INITIAL_INTERVAL, defaultValue); - } - - /** - * Return the 'backOffMultiplier' property or the default value. - * This is used in the retry template's ExponentialBackOffPolicy - * in binders that support retry. - * @param defaultValue The default value. - * @return The property or default value. - */ - public double getBackOffMultiplier(double defaultValue) { - return getProperty(BinderPropertyKeys.BACK_OFF_MULTIPLIER, defaultValue); - } - - /** - * Return the 'backOffMaxInterval' property or the default value. - * This is used in the retry template's ExponentialBackOffPolicy - * in binders that support retry. - * @param defaultValue The default value. - * @return The property or default value. - */ - public long getBackOffMaxInterval(long defaultValue) { - return getProperty(BinderPropertyKeys.BACK_OFF_MAX_INTERVAL, defaultValue); - } - - // Partitioning - - /** - * A class name for extracting partition keys from messages. - * @return The class name, - */ - public String getPartitionKeyExtractorClass() { - return getProperty(BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS); - } - - /** - * The expression to determine the partition key, evaluated against the - * message as the root object. - * @return The key. - */ - public Expression getPartitionKeyExpression() { - String partionKeyExpression = getProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION); - Expression expression = null; - if (partionKeyExpression != null) { - expression = spelExpressionParser.parseExpression(partionKeyExpression); - } - return expression; - } - - /** - * A class name for calculating a partition from a key. - * @return The class name, - */ - public String getPartitionSelectorClass() { - return getProperty(BinderPropertyKeys.PARTITION_SELECTOR_CLASS); - } - - /** - * The expression evaluated against the partition key to determine - * the partition to which the message will be sent. The result should - * be an integer that will subsequently be mod'd with the module's - * partition count. - * @return The expression. - */ - public Expression getPartitionSelectorExpression() { - String partionSelectorExpression = getProperty(BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION); - Expression expression = null; - if (partionSelectorExpression != null) { - expression = spelExpressionParser.parseExpression(partionSelectorExpression); - } - return expression; - } - - /** - * The sequence number for this module. - * - * @return the sequence number. - */ - public int getSequence() { - return getProperty(BinderPropertyKeys.SEQUENCE, 1); - } - - /** - * The module count. - * - * @return the module count. - */ - public int getCount() { - return getProperty(BinderPropertyKeys.COUNT, 1); - } - - /** - * The next module count for non-sink modules - * @return the next module count - */ - public int getNextModuleCount() { - return getProperty(BinderPropertyKeys.NEXT_MODULE_COUNT, 1); - } - - /** - * The partition index that this consumer supports. - * @return The partition index. - */ - public int getPartitionIndex() { - return getProperty(BinderPropertyKeys.PARTITION_INDEX, -1); - } - - // Batching - - /** - * If true, enable batching. - * @param defaultValue the default value. - * @return the property or default value. - */ - public boolean isBatchingEnabled(boolean defaultValue) { - return getProperty(BinderPropertyKeys.BATCHING_ENABLED, defaultValue); - } - - /** - * The batch size. - * @param defaultValue the default value. - * @return the property or default value. - */ - public int getBatchSize(int defaultValue) { - return getProperty(BinderPropertyKeys.BATCH_SIZE, defaultValue); - } - - /** - * The batch buffer limit. - * @param defaultValue the default value. - * @return the property or default value. - */ - public int geteBatchBufferLimit(int defaultValue) { - return getProperty(BinderPropertyKeys.BATCH_BUFFER_LIMIT, defaultValue); - } - - /** - * The batch timeout. - * @param defaultValue the default value. - * @return the property or default value. - */ - public long getBatchTimeout(long defaultValue) { - return getProperty(BinderPropertyKeys.BATCH_TIMEOUT, defaultValue); - } - - /** - * If true, messages will be compressed. - * @param defaultValue the default value. - * @return the property or default value. - */ - public boolean isCompress(boolean defaultValue) { - return getProperty(BinderPropertyKeys.COMPRESS, defaultValue); - } - - /** - * A list of groups for which the binder will ensure message delivery, even if their consumers bind - * after the producer. This is a producer-property only. - * @param defaultValue the default value - * @return the property, parsed as a comma-separated list of values - */ - public String[] getRequiredGroups(String[] defaultValue) { - String requiredGroupsValue = getProperty(BinderPropertyKeys.REQUIRED_GROUPS, ""); - return StringUtils.commaDelimitedListToStringArray(requiredGroupsValue); - } - - - // Utility methods - - /** - * Convert a comma-delimited String property to a String[] if - * present, or return the default value. - * @param value The property value. - * @param defaultValue The default value. - * @return The converted property or default value. - */ - protected String[] asStringArray(String value, String[] defaultValue) { - if (StringUtils.hasText(value)) { - return StringUtils.commaDelimitedListToStringArray(value); - } - else { - return defaultValue; - } - } - - @Override - public String toString() { - return this.properties.toString(); - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/PartitionHandler.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/PartitionHandler.java index 4bee24d87..9fc07bd1b 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/PartitionHandler.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/PartitionHandler.java @@ -18,11 +18,9 @@ package org.springframework.cloud.stream.binder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; import org.springframework.messaging.Message; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Utility class to determine if a binding is configured for partitioning @@ -44,7 +42,7 @@ public class PartitionHandler { private final PartitionSelectorStrategy partitionSelector; - private final PartitioningMetadata metadata; + private final ProducerProperties producerProperties; /** @@ -54,29 +52,18 @@ public class PartitionHandler { * @param evaluationContext evaluation context for binder * @param partitionSelector configured partition selector; may be {@code null} * @param properties binder properties - * @param partitionCount number of partitions configured for binder */ public PartitionHandler(ConfigurableListableBeanFactory beanFactory, - EvaluationContext evaluationContext, - PartitionSelectorStrategy partitionSelector, - DefaultBindingPropertiesAccessor properties, int partitionCount) { + EvaluationContext evaluationContext, + PartitionSelectorStrategy partitionSelector, + ProducerProperties properties) { Assert.notNull(beanFactory, "BeanFactory must not be null"); this.beanFactory = beanFactory; this.evaluationContext = evaluationContext; this.partitionSelector = partitionSelector == null ? new DefaultPartitionSelector() : partitionSelector; - this.metadata = new PartitioningMetadata(properties, partitionCount); - } - - /** - * Return {@code true} if the binder properties provided indicate - * that this binder is configured for partitioning. - * - * @return true if partitioning is enabled - */ - public boolean isPartitionedModule() { - return this.metadata.isPartitionedModule(); + this.producerProperties = properties; } /** @@ -101,27 +88,27 @@ public class PartitionHandler { Object key = extractKey(message); int partition; - if (this.metadata.hasSelectorClass()) { + if (this.producerProperties.getPartitionSelectorClass() != null) { partition = invokePartitionSelector(key); } - else if (this.metadata.hasSelectorExpression()) { - partition = this.metadata.partitionSelectorExpression.getValue( + else if (this.producerProperties.getPartitionSelectorExpression() != null) { + partition = this.producerProperties.getPartitionSelectorExpression().getValue( this.evaluationContext, key, Integer.class); } else { - partition = this.partitionSelector.selectPartition(key, metadata.partitionCount); + partition = this.partitionSelector.selectPartition(key, producerProperties.getPartitionCount()); } // protection in case a user selector returns a negative. - return Math.abs(partition % metadata.partitionCount); + return Math.abs(partition % producerProperties.getPartitionCount()); } private Object extractKey(Message message) { Object key = null; - if (this.metadata.hasKeyExtractorClass()) { + if (this.producerProperties.getPartitionKeyExtractorClass() != null) { key = invokeKeyExtractor(message); } - else if (this.metadata.hasKeyExpression()) { - key = this.metadata.partitionKeyExpression.getValue(this.evaluationContext, message); + else if (this.producerProperties.getPartitionKeyExpression() != null) { + key = this.producerProperties.getPartitionKeyExpression().getValue(this.evaluationContext, message); } Assert.notNull(key, "Partition key cannot be null"); @@ -130,16 +117,16 @@ public class PartitionHandler { private Object invokeKeyExtractor(Message message) { PartitionKeyExtractorStrategy strategy = getBean( - metadata.partitionKeyExtractorClass, + producerProperties.getPartitionKeyExtractorClass().getName(), PartitionKeyExtractorStrategy.class); return strategy.extractKey(message); } private int invokePartitionSelector(Object key) { PartitionSelectorStrategy strategy = getBean( - metadata.partitionSelectorClass, + producerProperties.getPartitionSelectorClass().getName(), PartitionSelectorStrategy.class); - return strategy.selectPartition(key, metadata.partitionCount); + return strategy.selectPartition(key, producerProperties.getPartitionCount()); } private T getBean(String className, Class type) { @@ -190,46 +177,4 @@ public class PartitionHandler { } - private static class PartitioningMetadata { - - private final String partitionKeyExtractorClass; - - private final Expression partitionKeyExpression; - - private final String partitionSelectorClass; - - private final Expression partitionSelectorExpression; - - private final int partitionCount; - - public PartitioningMetadata(DefaultBindingPropertiesAccessor properties, int partitionCount) { - this.partitionCount = partitionCount; - this.partitionKeyExtractorClass = properties.getPartitionKeyExtractorClass(); - this.partitionKeyExpression = properties.getPartitionKeyExpression(); - this.partitionSelectorClass = properties.getPartitionSelectorClass(); - this.partitionSelectorExpression = properties.getPartitionSelectorExpression(); - } - - public boolean isPartitionedModule() { - return StringUtils.hasText(this.partitionKeyExtractorClass) || this.partitionKeyExpression != null; - } - - public boolean hasSelectorClass() { - return StringUtils.hasText(this.partitionSelectorClass); - } - - public boolean hasKeyExtractorClass() { - return StringUtils.hasText(this.partitionKeyExtractorClass); - } - - public boolean hasSelectorExpression() { - return partitionSelectorExpression != null; - } - - public boolean hasKeyExpression() { - return partitionKeyExpression != null; - } - - } - } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java new file mode 100644 index 000000000..148ccbb45 --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder; + +import org.springframework.expression.Expression; + +/** + * Common producer properties. + * + * @author Marius Bogoevici + */ +public class ProducerProperties { + + private Expression partitionKeyExpression = null; + + private Class partitionKeyExtractorClass = null; + + private Class partitionSelectorClass = null; + + private Expression partitionSelectorExpression = null; + + private int partitionCount = 1; + + private String[] requiredGroups = new String[] {}; + + public Expression getPartitionKeyExpression() { + return partitionKeyExpression; + } + + public void setPartitionKeyExpression(Expression partitionKeyExpression) { + this.partitionKeyExpression = partitionKeyExpression; + } + + public Class getPartitionKeyExtractorClass() { + return partitionKeyExtractorClass; + } + + public void setPartitionKeyExtractorClass(Class partitionKeyExtractorClass) { + this.partitionKeyExtractorClass = partitionKeyExtractorClass; + } + + public boolean isPartitioned() { + return this.partitionKeyExpression != null || partitionKeyExtractorClass != null; + } + + public Class getPartitionSelectorClass() { + return partitionSelectorClass; + } + + public void setPartitionSelectorClass(Class partitionSelectorClass) { + this.partitionSelectorClass = partitionSelectorClass; + } + + public Expression getPartitionSelectorExpression() { + return partitionSelectorExpression; + } + + public void setPartitionSelectorExpression(Expression partitionSelectorExpression) { + this.partitionSelectorExpression = partitionSelectorExpression; + } + + public int getPartitionCount() { + return partitionCount; + } + + public void setPartitionCount(int partitionCount) { + this.partitionCount = partitionCount; + } + + public String[] getRequiredGroups() { + return requiredGroups; + } + + public void setRequiredGroups(String... requiredGroups) { + this.requiredGroups = requiredGroups; + } + +} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java index a2a522454..5f556c783 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java @@ -16,12 +16,11 @@ package org.springframework.cloud.stream.binding; -import java.util.Properties; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; import org.springframework.integration.channel.DirectChannel; import org.springframework.messaging.MessageChannel; @@ -35,7 +34,6 @@ import org.springframework.util.ObjectUtils; * resolves the channel from the bean factory and, if not present, creates a new channel * and adds it to the factory after binding it to the binder. The binder is optionally * determined with a prefix preceding a colon. - * * @author Mark Fisher * @author Gary Russell * @author Ilayaperumal Gopinathan @@ -50,9 +48,11 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina private ConfigurableListableBeanFactory beanFactory; - public BinderAwareChannelResolver(BinderFactory binderFactory, - ChannelBindingServiceProperties channelBindingServiceProperties, DynamicDestinationsBindable dynamicDestinationsBindable) { + public BinderAwareChannelResolver(BinderFactory binderFactory, ChannelBindingServiceProperties channelBindingServiceProperties, + DynamicDestinationsBindable dynamicDestinationsBindable) { Assert.notNull(binderFactory, "'binderFactory' cannot be null"); + Assert.notNull(channelBindingServiceProperties, "'channelBindingServiceProperties' cannot be null"); + Assert.notNull(dynamicDestinationsBindable, "'dynamicDestinationBindable' cannot be null"); this.binderFactory = binderFactory; this.channelBindingServiceProperties = channelBindingServiceProperties; this.dynamicDestinationsBindable = dynamicDestinationsBindable; @@ -67,11 +67,11 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina } @Override - public MessageChannel resolveDestination(String destinationName) { + public MessageChannel resolveDestination(String channelName) { MessageChannel channel = null; DestinationResolutionException destinationResolutionException; try { - return super.resolveDestination(destinationName); + return super.resolveDestination(channelName); } catch (DestinationResolutionException e) { destinationResolutionException = e; @@ -79,33 +79,38 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina synchronized (this) { if (this.beanFactory != null && this.binderFactory != null) { String[] dynamicDestinations = null; - Properties producerProperties = null; if (this.channelBindingServiceProperties != null) { dynamicDestinations = this.channelBindingServiceProperties.getDynamicDestinations(); - // TODO: need the props to return some defaults if not found - producerProperties = this.channelBindingServiceProperties.getProducerProperties(destinationName); } boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations) - || ObjectUtils.containsElement(dynamicDestinations, destinationName); + || ObjectUtils.containsElement(dynamicDestinations, channelName); if (dynamicAllowed) { - String transport = null; - String beanName = destinationName; - if (destinationName.contains(":")) { - String[] tokens = destinationName.split(":", 2); + String binderName = null; + String beanName = channelName; + if (channelName.contains(":")) { + String[] tokens = channelName.split(":", 2); if (tokens.length == 2) { - transport = tokens[0]; - destinationName = tokens[1]; + binderName = tokens[0]; + channelName = tokens[1]; } else if (tokens.length != 1) { - throw new IllegalArgumentException("Unrecognized channel naming scheme: " + destinationName + " , should be" + - " [:]"); + throw new IllegalArgumentException("Unrecognized channel naming scheme: " + channelName + " , should be" + + " [:]"); } } channel = new DirectChannel(); this.beanFactory.registerSingleton(beanName, channel); channel = (MessageChannel) this.beanFactory.initializeBean(channel, beanName); - Binder binder = binderFactory.getBinder(transport); - this.dynamicDestinationsBindable.addOutputBinding(beanName, binder.bindProducer(destinationName, channel, producerProperties)); + @SuppressWarnings("unchecked") + Binder binder = + (Binder) binderFactory.getBinder(binderName); + Class producerPropertiesClass = + ChannelBindingService.resolveProducerPropertiesType(binder); + ProducerProperties producerProperties = + this.channelBindingServiceProperties.getProducerProperties(channelName, producerPropertiesClass); + String destinationName = this.channelBindingServiceProperties.getBindingDestination(channelName); + this.dynamicDestinationsBindable.addOutputBinding(beanName, + binder.bindProducer(destinationName, channel, producerProperties)); } else { throw destinationResolutionException; diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java index 3b544bba2..5015f5f10 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/ChannelBindingService.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,8 +28,10 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binder.Binding; -import org.springframework.cloud.stream.config.BindingProperties; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; +import org.springframework.core.ResolvableType; import org.springframework.messaging.MessageChannel; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -63,29 +64,34 @@ public class ChannelBindingService { this.binderFactory = binderFactory; } + @SuppressWarnings("unchecked") public Collection> bindConsumer(MessageChannel inputChannel, String inputChannelName) { String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(inputChannelName); String[] channelBindingTargets = StringUtils.commaDelimitedListToStringArray(channelBindingTarget); List> bindings = new ArrayList<>(); - - Binder binder = getBinderForChannel(inputChannelName); - String consumerGroup = consumerGroup(inputChannelName); - Properties consumerProperties = this.channelBindingServiceProperties.getConsumerProperties(inputChannelName); - + Binder binder = + (Binder) getBinderForChannel(inputChannelName); + Class propertiesClass = resolveConsumerPropertiesType(binder); + ConsumerProperties consumerProperties = + this.channelBindingServiceProperties.getConsumerProperties(inputChannelName, propertiesClass); for (String target : channelBindingTargets) { - Binding binding = binder.bindConsumer(target, consumerGroup, inputChannel, - consumerProperties); + Binding binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName), + inputChannel, consumerProperties); bindings.add(binding); } this.consumerBindings.put(inputChannelName, bindings); return bindings; } + @SuppressWarnings("unchecked") public Binding bindProducer(MessageChannel outputChannel, String outputChannelName) { String channelBindingTarget = this.channelBindingServiceProperties.getBindingDestination(outputChannelName); - Binder binder = getBinderForChannel(outputChannelName); - Binding binding = binder.bindProducer(channelBindingTarget, outputChannel, - this.channelBindingServiceProperties.getProducerProperties(outputChannelName)); + Binder binder = + (Binder) getBinderForChannel(outputChannelName); + Class propertiesClass = resolveProducerPropertiesType(binder); + ProducerProperties producerProperties = + this.channelBindingServiceProperties.getProducerProperties(outputChannelName, propertiesClass); + Binding binding = binder.bindProducer(channelBindingTarget, outputChannel, producerProperties); this.producerBindings.put(outputChannelName, binding); return binding; } @@ -112,15 +118,49 @@ public class ChannelBindingService { } } - private Binder getBinderForChannel(String channelName) { + private Binder getBinderForChannel(String channelName) { String transport = this.channelBindingServiceProperties.getBinder(channelName); return binderFactory.getBinder(transport); } - private String consumerGroup(String inputChannelName) { - BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindings() - .get(inputChannelName); - return bindingProperties == null ? null : bindingProperties.getGroup(); + + static Class resolveConsumerPropertiesType(Binder binder) { + return resolveTypeForBinder(binder, ConsumerProperties.class); } + static Class resolveProducerPropertiesType(Binder binder) { + return resolveTypeForBinder(binder, ProducerProperties.class); + } + + @SuppressWarnings("unchecked") + static Class resolveTypeForBinder(Binder binder, Class upperBound) { + Class propertiesClass = null; + ResolvableType currentType = ResolvableType.forType(binder.getClass()); + while (!Object.class.equals(currentType.getRawClass()) && propertiesClass == null) { + ResolvableType[] interfaces = currentType.getInterfaces(); + ResolvableType binderResolvableType = null; + for (ResolvableType interfaceType : interfaces) { + if (Binder.class.equals(interfaceType.getRawClass())) { + binderResolvableType = interfaceType; + break; + } + } + if (binderResolvableType == null) { + currentType = currentType.getSuperType(); + } + else { + ResolvableType[] generics = binderResolvableType.getGenerics(); + for (ResolvableType generic : generics) { + Class resolvedParameter = generic.resolve(); + if (resolvedParameter != null && upperBound.isAssignableFrom(resolvedParameter)) { + propertiesClass = (Class) resolvedParameter; + } + } + } + } + if (propertiesClass == null) { + propertiesClass = upperBound; + } + return propertiesClass; + } } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java index f66dd5a20..ff3081489 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java @@ -91,7 +91,7 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea public void configureMessageChannel(MessageChannel channel, String channelName) { Assert.isAssignable(AbstractMessageChannel.class, channel.getClass()); AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel; - BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindings().get(channelName); + BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindingProperties(channelName); if (bindingProperties != null) { String contentType = bindingProperties.getContentType(); if (StringUtils.hasText(contentType)) { diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageHistoryTrackerConfigurer.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageHistoryTrackerConfigurer.java index faebb489e..267881706 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageHistoryTrackerConfigurer.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageHistoryTrackerConfigurer.java @@ -52,7 +52,7 @@ public class MessageHistoryTrackerConfigurer implements MessageChannelConfigurer @Override public void configureMessageChannel(MessageChannel messageChannel, String channelName) { - BindingProperties bindingProperties = channelBindingServiceProperties.getBindings().get(channelName); + BindingProperties bindingProperties = channelBindingServiceProperties.getBindingProperties(channelName); if (bindingProperties != null && Boolean.TRUE.equals(bindingProperties.isTrackHistory())) { final Set trackHistoryProperties = StringUtils.commaDelimitedListToSet(bindingProperties.getTrackedProperties()); Map channelBindingServicePropertiesMap = channelBindingServiceProperties.asMapProperties(); diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java index fb2feb648..01057f4b6 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java @@ -16,14 +16,11 @@ package org.springframework.cloud.stream.config; -import org.springframework.util.StringUtils; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * Contains the properties of a binding. - * * @author Marius Bogoevici * @author Ilayaperumal Gopinathan * @author Gary Russell @@ -41,9 +38,8 @@ public class BindingProperties { /** * Unique name that the binding belongs to (applies to consumers only). Multiple consumers within the same group * share the subscription. A null or empty String value indicates an anonymous group that is not shared. - * * @see org.springframework.cloud.stream.binder.Binder#bindConsumer(java.lang.String, java.lang.String, - * java.lang.Object, java.util.Properties) + * java.lang.Object, org.springframework.cloud.stream.binder.ConsumerProperties) */ private String group; @@ -65,44 +61,6 @@ public class BindingProperties { */ private String trackedProperties = "all"; - // Outbound properties - - private String requiredGroups; - - // Partition properties - - private String partitionKeyExpression; - - private String partitionKeyExtractorClass; - - private String partitionSelectorClass; - - private String partitionSelectorExpression; - - private Integer partitionCount = 1; - - private Integer nextModuleCount; - - private Integer nextModuleConcurrency; - - // Batching properties - private Boolean batchingEnabled; - - private Integer batchSize; - - private Integer batchBufferLimit; - - private Integer batchTimeout; - - // Inbound properties - private Integer concurrency; - - // Partition properties - private String partitionIndex; - - private Boolean partitioned = false; - - public String getDestination() { return this.destination; } @@ -143,126 +101,6 @@ public class BindingProperties { this.trackHistory = trackHistory; } - public String getPartitionKeyExpression() { - return this.partitionKeyExpression; - } - - public void setPartitionKeyExpression(String partitionKeyExpression) { - this.partitionKeyExpression = partitionKeyExpression; - } - - public String getPartitionKeyExtractorClass() { - return this.partitionKeyExtractorClass; - } - - public void setPartitionKeyExtractorClass(String partitionKeyExtractorClass) { - this.partitionKeyExtractorClass = partitionKeyExtractorClass; - } - - public String getPartitionSelectorClass() { - return this.partitionSelectorClass; - } - - public void setPartitionSelectorClass(String partitionSelectorClass) { - this.partitionSelectorClass = partitionSelectorClass; - } - - public String getPartitionSelectorExpression() { - return this.partitionSelectorExpression; - } - - public void setPartitionSelectorExpression(String partitionSelectorExpression) { - this.partitionSelectorExpression = partitionSelectorExpression; - } - - public Integer getNextModuleCount() { - return this.nextModuleCount; - } - - public void setNextModuleCount(Integer nextModuleCount) { - this.nextModuleCount = nextModuleCount; - } - - public Integer getNextModuleConcurrency() { - return this.nextModuleConcurrency; - } - - public void setNextModuleConcurrency(Integer nextModuleConcurrency) { - this.nextModuleConcurrency = nextModuleConcurrency; - } - - public Boolean isBatchingEnabled() { - return this.batchingEnabled; - } - - public void setBatchingEnabled(Boolean batchingEnabled) { - this.batchingEnabled = batchingEnabled; - } - - public Integer getBatchSize() { - return this.batchSize; - } - - public void setBatchSize(Integer batchSize) { - this.batchSize = batchSize; - } - - public Integer getBatchBufferLimit() { - return this.batchBufferLimit; - } - - public void setBatchBufferLimit(Integer batchBufferLimit) { - this.batchBufferLimit = batchBufferLimit; - } - - public Integer getBatchTimeout() { - return this.batchTimeout; - } - - public void setBatchTimeout(Integer batchTimeout) { - this.batchTimeout = batchTimeout; - } - - public Integer getPartitionCount() { - return this.partitionCount; - } - - public void setPartitionCount(Integer partitionCount) { - this.partitionCount = partitionCount; - } - - public Integer getConcurrency() { - return this.concurrency; - } - - public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; - } - - public String getPartitionIndex() { - return this.partitionIndex; - } - - public void setPartitionIndex(String partitionIndex) { - this.partitionIndex = partitionIndex; - } - - public Boolean isPartitioned() { - return this.partitioned; - } - - public void setPartitioned(Boolean partitioned) { - this.partitioned = partitioned; - } - - public String getRequiredGroups() { - return requiredGroups; - } - - public void setRequiredGroups(String requiredGroups) { - this.requiredGroups = requiredGroups; - } - public String getTrackedProperties() { return this.trackedProperties; } @@ -289,66 +127,6 @@ public class BindingProperties { sb.append("trackHistory=" + this.trackHistory); sb.append(COMMA); } - if (this.partitionKeyExpression != null && !this.partitionKeyExpression.isEmpty()) { - sb.append("partitionKeyExpression=" + partitionKeyExpression); - sb.append(COMMA); - } - if (this.partitionKeyExtractorClass != null && !this.partitionKeyExtractorClass.isEmpty()) { - sb.append("partitionKeyExtractorClass=" + partitionKeyExtractorClass); - sb.append(COMMA); - } - if (this.partitionSelectorClass != null && !this.partitionSelectorClass.isEmpty()) { - sb.append("partitionSelectorClass=" + partitionSelectorClass); - sb.append(COMMA); - } - if (this.partitionSelectorExpression != null && !this.partitionSelectorExpression.isEmpty()) { - sb.append("partitionSelectorExpression=" + partitionSelectorExpression); - sb.append(COMMA); - } - if (this.partitionCount != null) { - sb.append("partitionCount=" + this.partitionCount); - sb.append(COMMA); - } - if (this.nextModuleCount != null) { - sb.append("nextModuleCount=" + this.nextModuleCount); - sb.append(COMMA); - } - if (this.nextModuleConcurrency != null) { - sb.append("nextModuleConcurrency=" + this.nextModuleConcurrency); - sb.append(COMMA); - } - if (this.batchingEnabled != null) { - sb.append("batchingEnabled=" + this.batchingEnabled); - sb.append(COMMA); - } - if (this.batchSize != null) { - sb.append("batchSize=" + this.batchSize); - sb.append(COMMA); - } - if (this.batchBufferLimit != null) { - sb.append("batchBufferLimit=" + this.batchBufferLimit); - sb.append(COMMA); - } - if (this.batchTimeout != null) { - sb.append("batchTimeout=" + this.batchTimeout); - sb.append(COMMA); - } - if (this.partitioned != null) { - sb.append("partitioned=" + this.partitioned); - sb.append(COMMA); - } - if (this.partitionIndex != null) { - sb.append("partitionIndex=" + this.partitionIndex); - sb.append(COMMA); - } - if (this.concurrency != null) { - sb.append("concurrency=" + this.concurrency); - sb.append(COMMA); - } - if (!StringUtils.isEmpty(requiredGroups)) { - sb.append("requiredGroups=" + requiredGroups); - sb.append(COMMA); - } sb.deleteCharAt(sb.lastIndexOf(COMMA)); return "BindingProperties{" + sb.toString() + "}"; } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingPropertiesConverter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingPropertiesConverter.java deleted file mode 100644 index 83a056fc1..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingPropertiesConverter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.config; - -import org.springframework.core.convert.converter.Converter; - -/** - * Converter that transforms {@link String} expressions into {@link BindingProperties}. Useful for shorthand - * binding property configuration. - * - * @author Marius Bogoevici - */ -public class BindingPropertiesConverter implements Converter { - - public BindingPropertiesConverter() { - } - - @Override - public BindingProperties convert(String bindingConfiguration) { - BindingProperties bindingProperties = new BindingProperties(); - // for now, just configure the destination - in the future do some more advanced parsing - bindingProperties.setDestination(bindingConfiguration); - return bindingProperties; - } -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java index 1da49f774..75491ac82 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceConfiguration.java @@ -29,7 +29,6 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binding.BindableChannelFactory; @@ -49,7 +48,6 @@ import org.springframework.cloud.stream.binding.SingleChannelBindable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; -import org.springframework.core.convert.converter.Converter; import org.springframework.expression.PropertyAccessor; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean; @@ -140,11 +138,6 @@ public class ChannelBindingServiceConfiguration { return new BinderAwareChannelResolver(binderFactory, channelBindingServiceProperties, dynamicBindable()); } - @Bean - @ConfigurationPropertiesBinding - public Converter bindingPropertiesConverter() { - return new BindingPropertiesConverter(); - } @Bean @ConditionalOnProperty("spring.cloud.stream.bindings." + ERROR_CHANNEL_NAME + ".destination") diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java index b7c4b7957..f983920f8 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ChannelBindingServiceProperties.java @@ -16,19 +16,36 @@ package org.springframework.cloud.stream.config; +import java.beans.PropertyDescriptor; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; -import org.springframework.util.StringUtils; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.bind.PropertySourcesPropertyValues; +import org.springframework.boot.bind.RelaxedDataBinder; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.convert.ConversionService; +import org.springframework.integration.support.utils.IntegrationUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + /** * @author Dave Syer * @author Marius Bogoevici @@ -37,7 +54,20 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; */ @ConfigurationProperties("spring.cloud.stream") @JsonInclude(Include.NON_DEFAULT) -public class ChannelBindingServiceProperties { +public class ChannelBindingServiceProperties implements ApplicationContextAware, InitializingBean { + + private final static String[] bindingPropertyFields; + + static { + PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(BindingProperties.class); + List propertyNames = new ArrayList<>(); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + propertyNames.add(propertyDescriptor.getName()); + } + bindingPropertyFields = propertyNames.toArray(new String[propertyNames.size()]); + } + + private ConversionService conversionService; @Value("${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}") private int instanceIndex = 0; @@ -48,10 +78,18 @@ public class ChannelBindingServiceProperties { private Map binders = new HashMap<>(); + private Properties consumerDefaults = new Properties(); + + private Properties producerDefaults = new Properties(); + private String defaultBinder; private String[] dynamicDestinations = new String[0]; + private boolean ignoreUnknownProperties = true; + + private ConfigurableApplicationContext applicationContext; + public Map getBindings() { return bindings; } @@ -100,128 +138,44 @@ public class ChannelBindingServiceProperties { this.dynamicDestinations = dynamicDestinations; } - public String getBindingDestination(String channelName) { - BindingProperties bindingProperties = bindings.get(channelName); - // we may shortcut directly to the path - // just return the channel name if not found - return bindingProperties != null && StringUtils.hasText(bindingProperties.getDestination()) ? - bindingProperties.getDestination() : channelName; + public Properties getConsumerDefaults() { + return consumerDefaults; } - /** - * Get consumer properties for the given input channel name. - * - * @param inputChannelName the input channel name - * @return merged consumer properties - */ - public Properties getConsumerProperties(String inputChannelName) { - Properties channelConsumerProperties = new Properties(); - BindingProperties bindingProperties = this.bindings.get(inputChannelName); - if (bindingProperties != null) { - if (bindingProperties.getConcurrency() != null) { - channelConsumerProperties.setProperty(BinderPropertyKeys.CONCURRENCY, - Integer.toString(bindingProperties.getConcurrency())); - } - updateConsumerPartitionProperties(inputChannelName, channelConsumerProperties); - } - return channelConsumerProperties; + public void setConsumerDefaults(Properties consumerDefaults) { + this.consumerDefaults = consumerDefaults; } - /** - * Get producer properties for the given output channel name. - * - * @param outputChannelName the output channel name - * @return merged producer properties - */ - public Properties getProducerProperties(String outputChannelName) { - Properties channelProducerProperties = new Properties(); - BindingProperties bindingProperties = this.bindings.get(outputChannelName); - if (bindingProperties != null) { - updateBatchProperties(bindingProperties, channelProducerProperties); - updateProducerPartitionProperties(bindingProperties, channelProducerProperties); - if (StringUtils.hasText(bindingProperties.getRequiredGroups())) { - channelProducerProperties.setProperty(BinderPropertyKeys.REQUIRED_GROUPS, - bindingProperties.getRequiredGroups()); - } - } - return channelProducerProperties; + public Properties getProducerDefaults() { + return producerDefaults; } - private boolean isPartitionedConsumer(String channelName) { - BindingProperties bindingProperties = bindings.get(channelName); - return bindingProperties != null && bindingProperties.isPartitioned(); + public void setProducerDefaults(Properties producerDefaults) { + this.producerDefaults = producerDefaults; } - private boolean isPartitionedProducer(BindingProperties bindingProperties) { - return (StringUtils.hasText(bindingProperties.getPartitionKeyExpression()) - || StringUtils.hasText(bindingProperties.getPartitionKeyExtractorClass())); + public boolean isIgnoreUnknownProperties() { + return ignoreUnknownProperties; } - private void updateBatchProperties(BindingProperties bindingProperties, Properties producerProperties) { - if (bindingProperties.isBatchingEnabled() != null) { - producerProperties.setProperty(BinderPropertyKeys.BATCHING_ENABLED, - String.valueOf(bindingProperties.isBatchingEnabled())); - } - if (bindingProperties.getBatchSize() != null) { - producerProperties.setProperty(BinderPropertyKeys.BATCH_SIZE, - String.valueOf(bindingProperties.getBatchSize())); - } - if (bindingProperties.getBatchBufferLimit() != null) { - producerProperties.setProperty(BinderPropertyKeys.BATCH_BUFFER_LIMIT, - String.valueOf(bindingProperties.getBatchBufferLimit())); - } - if (bindingProperties.getBatchTimeout() != null) { - producerProperties.setProperty(BinderPropertyKeys.BATCH_TIMEOUT, - String.valueOf(bindingProperties.getBatchTimeout())); - } + public void setIgnoreUnknownProperties(boolean ignoreUnknownProperties) { + this.ignoreUnknownProperties = ignoreUnknownProperties; } - private void updateProducerPartitionProperties(BindingProperties bindingProperties, Properties producerProperties) { - if (isPartitionedProducer(bindingProperties)) { - if (bindingProperties.getPartitionKeyExpression() != null) { - producerProperties.setProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION, - bindingProperties.getPartitionKeyExpression()); - } - if (bindingProperties.getPartitionKeyExtractorClass() != null) { - producerProperties.setProperty(BinderPropertyKeys.PARTITION_KEY_EXTRACTOR_CLASS, - bindingProperties.getPartitionKeyExtractorClass()); - } - if (bindingProperties.getPartitionSelectorClass() != null) { - producerProperties.setProperty(BinderPropertyKeys.PARTITION_SELECTOR_CLASS, - bindingProperties.getPartitionSelectorClass()); - } - if (bindingProperties.getPartitionSelectorExpression() != null) { - producerProperties.setProperty(BinderPropertyKeys.PARTITION_SELECTOR_EXPRESSION, - bindingProperties.getPartitionSelectorExpression()); - } - if (bindingProperties.getPartitionCount() != null) { - producerProperties.setProperty(BinderPropertyKeys.NEXT_MODULE_COUNT, - Integer.toString(bindingProperties.getPartitionCount())); - } - if (bindingProperties.getNextModuleConcurrency() != null) { - producerProperties.setProperty(BinderPropertyKeys.NEXT_MODULE_CONCURRENCY, - Integer.toString(bindingProperties.getNextModuleConcurrency())); - } - } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; } - private void updateConsumerPartitionProperties(String inputChannelName, Properties consumerProperties) { - BindingProperties bindingProperties = this.bindings.get(inputChannelName); - if (bindingProperties != null) { - if (isPartitionedConsumer(inputChannelName)) { - consumerProperties.setProperty(BinderPropertyKeys.COUNT, - Integer.toString(getInstanceCount())); - consumerProperties.setProperty(BinderPropertyKeys.PARTITION_INDEX, - Integer.toString(getInstanceIndex())); - } + @Override + public void afterPropertiesSet() throws Exception { + if (conversionService == null) { + conversionService = applicationContext.getBean(IntegrationUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); } } public String getBinder(String channelName) { - if (!bindings.containsKey(channelName)) { - return null; - } - return bindings.get(channelName).getBinder(); + return getBindingProperties(channelName).getBinder(); } /** @@ -234,15 +188,72 @@ public class ChannelBindingServiceProperties { properties.put("instanceCount", String.valueOf(getInstanceCount())); properties.put("defaultBinder", getDefaultBinder()); properties.put("dynamicDestinations", getDynamicDestinations()); - // Add Bindings properties - for (Map.Entry entry : getBindings().entrySet()) { + for (Map.Entry entry : bindings.entrySet()) { properties.put(entry.getKey(), entry.getValue().toString()); } - // Add Binder config properties for (Map.Entry entry : binders.entrySet()) { properties.put(entry.getKey(), entry.getValue()); } return properties; } + public T getConsumerProperties(String inputChannelName, Class beanClass) { + Assert.notNull(inputChannelName, "The input channel name cannot be null"); + Assert.notNull(beanClass, "The bean class cannot be null"); + T consumerProperties = populateProperties(inputChannelName, beanClass, consumerDefaults); + consumerProperties.setInstanceCount(this.instanceCount); + consumerProperties.setInstanceIndex(this.instanceIndex); + return consumerProperties; + } + + + public T getProducerProperties(String outputChannelName, Class beanClass) { + Assert.notNull(outputChannelName, "The output channel name cannot be null"); + Assert.notNull(beanClass, "The bean class cannot be null"); + T producerProperties = populateProperties(outputChannelName, beanClass, producerDefaults); + return producerProperties; + } + + + private C populateProperties(String channelName, Class propertiesClass, Properties defaults) { + C beanInstance; + try { + beanInstance = propertiesClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) { + throw new BeanInitializationException(e.getMessage()); + } + // bind defaults first + RelaxedDataBinder dataBinder = new RelaxedDataBinder(beanInstance); + dataBinder.setIgnoreUnknownFields(ignoreUnknownProperties); + dataBinder.setDisallowedFields(bindingPropertyFields); + dataBinder.bind(new MutablePropertyValues(defaults)); + // bind configured properties next, if available + if (applicationContext != null && applicationContext.getEnvironment() != null) { + dataBinder = new RelaxedDataBinder(beanInstance, "spring.cloud.stream.bindings." + channelName); + dataBinder.setConversionService(conversionService); + dataBinder.setIgnoreUnknownFields(ignoreUnknownProperties); + dataBinder.setDisallowedFields(bindingPropertyFields); + dataBinder.bind(new PropertySourcesPropertyValues(applicationContext.getEnvironment().getPropertySources())); + } + return beanInstance; + } + + public BindingProperties getBindingProperties(String channelName) { + BindingProperties bindingProperties = bindings.containsKey(channelName) ? + bindings.get(channelName) : new BindingProperties(); + return bindingProperties; + } + + public String getGroup(String channelName) { + return getBindingProperties(channelName).getGroup(); + } + + public String getBindingDestination(String channelName) { + BindingProperties bindingProperties = getBindingProperties(channelName); + if (bindingProperties != null && StringUtils.hasText(bindingProperties.getDestination())) { + return bindingProperties.getDestination(); + } + return channelName; + } } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfiguration.java index 8a272cbad..da1256a4e 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.config.IntegrationConverter; import org.springframework.integration.context.IntegrationContextUtils; /** @@ -38,7 +39,7 @@ import org.springframework.integration.context.IntegrationContextUtils; public class SpelExpressionConverterConfiguration { @Bean - @ConfigurationPropertiesBinding + @ConfigurationPropertiesBinding @IntegrationConverter public Converter spelConverter() { return new SpelConverter(); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java index fd3db7c23..a63f224cf 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/endpoint/ChannelsEndpoint.java @@ -20,16 +20,16 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.springframework.boot.actuate.endpoint.AbstractEndpoint; -import org.springframework.cloud.stream.binding.Bindable; -import org.springframework.cloud.stream.config.BindingProperties; -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.cloud.stream.binding.Bindable; +import org.springframework.cloud.stream.config.BindingProperties; +import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; + /** * @author Dave Syer */ @@ -68,7 +68,9 @@ public class ChannelsEndpoint extends AbstractEndpoint> { @JsonInclude(value = Include.NON_DEFAULT) public static class ChannelsMetaData { + private Map inputs = new LinkedHashMap<>(); + private Map outputs = new LinkedHashMap<>(); public Map getInputs() { @@ -88,4 +90,4 @@ public class ChannelsEndpoint extends AbstractEndpoint> { } } -} \ No newline at end of file +} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithBindingTargets.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithBindingTargets.java index ad00daa50..fbf79e357 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithBindingTargets.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithBindingTargets.java @@ -21,8 +21,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.util.Properties; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -55,10 +53,10 @@ public class ArbitraryInterfaceBindingTestsWithBindingTargets { @SuppressWarnings("unchecked") @Test public void testArbitraryInterfaceChannelsBound() { - verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(fooChannels.foo()), Mockito.any()); - verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(fooChannels.bar()), Mockito.any()); - verify(binder).bindProducer(eq("someQueue.2"), eq(fooChannels.baz()), Mockito.any()); - verify(binder).bindProducer(eq("someQueue.3"), eq(fooChannels.qux()), Mockito.any()); + verify(binder).bindConsumer(eq("someQueue.0"), anyString(), eq(fooChannels.foo()), Mockito.any()); + verify(binder).bindConsumer(eq("someQueue.1"), anyString(), eq(fooChannels.bar()), Mockito.any()); + verify(binder).bindProducer(eq("someQueue.2"), eq(fooChannels.baz()), Mockito.any()); + verify(binder).bindProducer(eq("someQueue.3"), eq(fooChannels.qux()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithDefaults.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithDefaults.java index 874f2bb3f..9f5a0fe10 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithDefaults.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ArbitraryInterfaceBindingTestsWithDefaults.java @@ -54,10 +54,10 @@ public class ArbitraryInterfaceBindingTestsWithDefaults { @SuppressWarnings("unchecked") @Test public void testArbitraryInterfaceChannelsBound() { - verify(binder).bindConsumer(eq("foo"), anyString(), eq(fooChannels.foo()), Mockito.any()); - verify(binder).bindConsumer(eq("bar"), anyString(), eq(fooChannels.bar()), Mockito.any()); - verify(binder).bindProducer(eq("baz"), eq(fooChannels.baz()), Mockito.any()); - verify(binder).bindProducer(eq("qux"), eq(fooChannels.qux()), Mockito.any()); + verify(binder).bindConsumer(eq("foo"), anyString(), eq(fooChannels.foo()), Mockito.any()); + verify(binder).bindConsumer(eq("bar"), anyString(), eq(fooChannels.bar()), Mockito.any()); + verify(binder).bindProducer(eq("baz"), eq(fooChannels.baz()), Mockito.any()); + verify(binder).bindProducer(eq("qux"), eq(fooChannels.qux()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java index f00bac5c0..9f580c4c0 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -68,18 +67,19 @@ public class BinderAwareChannelResolverTests { private volatile BinderAwareChannelResolver resolver; - private volatile Binder binder; + private volatile Binder binder; @Before public void setupContext() throws Exception { this.binder = new TestBinder(); BinderFactory binderFactory = new BinderFactory() { + @Override - public Binder getBinder(String configurationName) { + public Binder getBinder(String configurationName) { return binder; } }; - this.resolver = new BinderAwareChannelResolver(binderFactory, null, new DynamicDestinationsBindable()); + this.resolver = new BinderAwareChannelResolver(binderFactory, new ChannelBindingServiceProperties(), new DynamicDestinationsBindable()); this.resolver.setBeanFactory(context.getBeanFactory()); context.getBeanFactory().registerSingleton("channelResolver", this.resolver); @@ -94,7 +94,7 @@ public class BinderAwareChannelResolverTests { MessageChannel registered = resolver.resolveDestination("foo"); DirectChannel testChannel = new DirectChannel(); final CountDownLatch latch = new CountDownLatch(1); - final List> received = new ArrayList>(); + final List> received = new ArrayList<>(); testChannel.subscribe(new MessageHandler() { @Override @@ -103,7 +103,7 @@ public class BinderAwareChannelResolverTests { latch.countDown(); } }); - binder.bindConsumer("foo", null, testChannel, null); + binder.bindConsumer("foo", null, testChannel, new ConsumerProperties()); assertEquals(0, received.size()); registered.send(MessageBuilder.withPayload("hello").build()); try { @@ -125,25 +125,24 @@ public class BinderAwareChannelResolverTests { } @Test - @SuppressWarnings("rawtypes") + @SuppressWarnings({"rawtypes", "unchecked"}) public void propertyPassthrough() { ChannelBindingServiceProperties bindingServiceProperties = new ChannelBindingServiceProperties(); DynamicDestinationsBindable dynamicDestinationsBindable = new DynamicDestinationsBindable(); - Map bindings = new HashMap(); - BindingProperties bindingProperties = new BindingProperties(); - bindingProperties.setContentType("text/plain"); - bindings.put("foo", bindingProperties); + Map bindings = new HashMap<>(); + BindingProperties genericProperties = new BindingProperties(); + bindings.put("foo", genericProperties); bindingServiceProperties.setBindings(bindings); @SuppressWarnings("unchecked") - Binder binder = mock(Binder.class); - Binder binder2 = mock(Binder.class); - BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class); + Binder binder = mock(Binder.class); + Binder binder2 = mock(Binder.class); + BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class); Binding fooBinding = Mockito.mock(Binding.class); Binding barBinding = Mockito.mock(Binding.class); when(binder.bindProducer( - matches("foo"), any(DirectChannel.class), any(Properties.class))).thenReturn(fooBinding); + matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(fooBinding); when(binder2.bindProducer( - matches("bar"), any(DirectChannel.class), any(Properties.class))).thenReturn(barBinding); + matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(barBinding); when(mockBinderFactory.getBinder(null)).thenReturn(binder); when(mockBinderFactory.getBinder("someTransport")).thenReturn(binder2); @SuppressWarnings("unchecked") @@ -152,10 +151,10 @@ public class BinderAwareChannelResolverTests { BeanFactory beanFactory = new DefaultListableBeanFactory(); resolver.setBeanFactory(beanFactory); MessageChannel resolved = resolver.resolveDestination("foo"); - verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), any(Properties.class)); + verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), any(ProducerProperties.class)); assertSame(resolved, beanFactory.getBean("foo")); resolved = resolver.resolveDestination("someTransport:bar"); - verify(binder2).bindProducer(eq("bar"), any(MessageChannel.class), any(Properties.class)); + verify(binder2).bindProducer(eq("bar"), any(MessageChannel.class), any(ProducerProperties.class)); assertSame(resolved, beanFactory.getBean("someTransport:bar")); assertTrue("Dynamic bindable should have two destination names", dynamicDestinationsBindable.getOutputs().size() == 2); assertTrue("Dynamic bindable should have the destination name 'foo'", dynamicDestinationsBindable.getOutputs().contains("foo")); @@ -165,13 +164,12 @@ public class BinderAwareChannelResolverTests { /** * A simple test binder that creates queues for the destinations. Ignores groups. */ - private class TestBinder implements Binder { + private class TestBinder implements Binder { private final Map destinations = new ConcurrentHashMap<>(); @Override - public Binding bindConsumer(String name, String group, MessageChannel inboundBindTarget, - Properties properties) { + public Binding bindConsumer(String name, String group, MessageChannel inboundBindTarget, ConsumerProperties properties) { synchronized (destinations) { if (!destinations.containsKey(name)) { destinations.put(name, new DirectChannel()); @@ -184,7 +182,7 @@ public class BinderAwareChannelResolverTests { @Override - public Binding bindProducer(String name, MessageChannel outboundBindTarget, Properties properties) { + public Binding bindProducer(String name, MessageChannel outboundBindTarget, ProducerProperties properties) { synchronized (destinations) { if (!destinations.containsKey(name)) { destinations.put(name, new DirectChannel()); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java index 75f71d6dc..0c566caf0 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderFactoryConfigurationTests.java @@ -38,14 +38,13 @@ import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.binder.stub1.StubBinder1; import org.springframework.cloud.stream.binder.stub1.StubBinder1Configuration; import org.springframework.cloud.stream.binder.stub2.StubBinder2; import org.springframework.cloud.stream.binder.stub2.StubBinder2ConfigurationA; import org.springframework.cloud.stream.binder.stub2.StubBinder2ConfigurationB; import org.springframework.cloud.stream.config.BinderFactoryConfiguration; -import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import; import org.springframework.core.io.ClassPathResource; @@ -207,7 +206,7 @@ public class BinderFactoryConfigurationTests { } @Import({BinderFactoryConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) - @EnableConfigurationProperties(ChannelBindingServiceProperties.class) + @EnableBinding public static class SimpleApplication { } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/DefaultSettingsTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/DefaultSettingsTests.java new file mode 100644 index 000000000..cf9b73a2c --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/DefaultSettingsTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binder; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsArrayContainingInOrder.arrayContaining; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.hamcrest.CoreMatchers; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Import; + +/** + * @author Marius Bogoevici + */ +public class DefaultSettingsTests { + + @SuppressWarnings("unchecked") + @Test + public void testDefaultSettings() { + ConfigurableApplicationContext applicationContext = createBuilder().run( + "--spring.cloud.stream.bindings.foo.destination=fooDest", + "--spring.cloud.stream.bindings.bar.destination=barDest", + "--spring.cloud.stream.bindings.baz.destination=bazDest", + "--spring.cloud.stream.bindings.qux.destination=quxDest", + "--spring.cloud.stream.bindings.foo.group=fooBarGroup", + "--spring.cloud.stream.bindings.bar.group=fooBarGroup", + "--spring.cloud.stream.consumerDefaults.concurrency=2", + "--spring.cloud.stream.producerDefaults.requiredGroups=quxbazReq1,quxbazReq2"); + + Binder binder = applicationContext.getBean(Binder.class); + FooChannels fooChannels = applicationContext.getBean(FooChannels.class); + + ArgumentCaptor fooConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class); + verify(binder).bindConsumer(eq("fooDest"), eq("fooBarGroup"), same(fooChannels.foo()), + fooConsumerProperties.capture()); + assertThat(fooConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2)); + ArgumentCaptor barConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class); + verify(binder).bindConsumer(eq("barDest"), eq("fooBarGroup"), same(fooChannels.bar()), + barConsumerProperties.capture()); + assertThat(barConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2)); + + ArgumentCaptor bazProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class); + verify(binder).bindProducer(eq("bazDest"), same(fooChannels.baz()), bazProducerProperties.capture()); + assertThat(bazProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2")); + + ArgumentCaptor quxProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class); + verify(binder).bindProducer(eq("quxDest"), same(fooChannels.qux()), quxProducerProperties.capture()); + assertThat(quxProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2")); + + verifyNoMoreInteractions(binder); + applicationContext.close(); + } + + @SuppressWarnings("unchecked") + @Test + public void testDefaultSettingsOverridden() { + ConfigurableApplicationContext applicationContext = createBuilder().run( + "--spring.cloud.stream.bindings.foo.destination=fooDest", + "--spring.cloud.stream.bindings.bar.destination=barDest", + "--spring.cloud.stream.bindings.baz.destination=bazDest", + "--spring.cloud.stream.bindings.qux.destination=quxDest", + "--spring.cloud.stream.bindings.foo.group=fooBarGroup", + "--spring.cloud.stream.bindings.bar.group=fooBarGroup", + "--spring.cloud.stream.consumerDefaults.concurrency=2", + "--spring.cloud.stream.bindings.bar.concurrency=4", + "--spring.cloud.stream.producerDefaults.requiredGroups=quxbazReq1,quxbazReq2", + "--spring.cloud.stream.bindings.qux.requiredGroups=quxbazReq3,quxbazReq4"); + + Binder binder = applicationContext.getBean(Binder.class); + FooChannels fooChannels = applicationContext.getBean(FooChannels.class); + + ArgumentCaptor fooConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class); + verify(binder).bindConsumer(eq("fooDest"), eq("fooBarGroup"), same(fooChannels.foo()), + fooConsumerProperties.capture()); + assertThat(fooConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(2)); + ArgumentCaptor barConsumerProperties = ArgumentCaptor.forClass(ConsumerProperties.class); + verify(binder).bindConsumer(eq("barDest"), eq("fooBarGroup"), same(fooChannels.bar()), + barConsumerProperties.capture()); + assertThat(barConsumerProperties.getValue().getConcurrency(), CoreMatchers.equalTo(4)); + + ArgumentCaptor bazProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class); + verify(binder).bindProducer(eq("bazDest"), same(fooChannels.baz()), bazProducerProperties.capture()); + assertThat(bazProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq1", "quxbazReq2")); + + ArgumentCaptor quxProducerProperties = ArgumentCaptor.forClass(ProducerProperties.class); + verify(binder).bindProducer(eq("quxDest"), same(fooChannels.qux()), quxProducerProperties.capture()); + assertThat(quxProducerProperties.getValue().getRequiredGroups(), arrayContaining("quxbazReq3", "quxbazReq4")); + + verifyNoMoreInteractions(binder); + applicationContext.close(); + } + + private SpringApplicationBuilder createBuilder() { + return new SpringApplicationBuilder(TestFooChannels.class) + .web(false); + } + + + @EnableBinding(FooChannels.class) + @EnableAutoConfiguration + @Import(MockBinderRegistryConfiguration.class) + public static class TestFooChannels { + + } +} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java index 20122b506..1f6195a89 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java @@ -48,10 +48,10 @@ public class ErrorBindingTests { BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class); @SuppressWarnings("unchecked") - Binder binder = (Binder) binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); - Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(Properties.class)); - Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(Properties.class)); + Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class)); + Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class)); Mockito.verifyNoMoreInteractions(binder); applicationContext.close(); } @@ -64,14 +64,14 @@ public class ErrorBindingTests { BinderFactory binderFactory = applicationContext.getBean(BinderFactory.class); @SuppressWarnings("unchecked") - Binder binder = (Binder) binderFactory.getBinder(null); + Binder binder = binderFactory.getBinder(null); MessageChannel errorChannel = applicationContext.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, MessageChannel.class); - Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(Properties.class)); - Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(Properties.class)); - Mockito.verify(binder).bindProducer(eq("foo"), same(errorChannel), any(Properties.class)); + Mockito.verify(binder).bindConsumer(eq("input"), isNull(String.class), any(MessageChannel.class), any(ConsumerProperties.class)); + Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class), any(ProducerProperties.class)); + Mockito.verify(binder).bindProducer(eq("foo"), same(errorChannel), any(ProducerProperties.class)); Mockito.verifyNoMoreInteractions(binder); applicationContext.close(); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java index b735b08ff..8e49c1ce4 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/InputOutputBindingOrderTest.java @@ -23,8 +23,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.util.Properties; - import org.junit.Test; import org.mockito.Mockito; @@ -52,7 +50,7 @@ public class InputOutputBindingOrderTest { Binder binder = applicationContext.getBean(BinderFactory.class).getBinder(null); Processor processor = applicationContext.getBean(Processor.class); // input is bound after the context has been started - verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.any()); + verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.any()); SomeLifecycle someLifecycle = applicationContext.getBean(SomeLifecycle.class); assertTrue(someLifecycle.isRunning()); applicationContext.close(); @@ -84,7 +82,7 @@ public class InputOutputBindingOrderTest { @Override @SuppressWarnings("unchecked") public synchronized void start() { - verify(this.binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.any()); + verify(this.binder).bindProducer(eq("output"), eq(this.processor.output()), Mockito.any()); // input was not bound yet verifyNoMoreInteractions(this.binder); this.running = true; diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithBindingTargets.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithBindingTargets.java index 07d18fbb5..79d888e17 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithBindingTargets.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithBindingTargets.java @@ -54,8 +54,8 @@ public class ProcessorBindingTestsWithBindingTargets { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - verify(binder).bindConsumer(eq("testtock.0"), anyString(), eq(testProcessor.input()), Mockito.any()); - verify(binder).bindProducer(eq("testtock.1"), eq(testProcessor.output()), Mockito.any()); + verify(binder).bindConsumer(eq("testtock.0"), anyString(), eq(testProcessor.input()), Mockito.any()); + verify(binder).bindProducer(eq("testtock.1"), eq(testProcessor.output()), Mockito.any()); } @EnableBinding(Processor.class) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithDefaults.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithDefaults.java index d1b21f746..090ab178c 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithDefaults.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ProcessorBindingTestsWithDefaults.java @@ -53,8 +53,8 @@ public class ProcessorBindingTestsWithDefaults { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.any()); - Mockito.verify(binder).bindProducer(eq("output"), eq(processor.output()), Mockito.any()); + Mockito.verify(binder).bindConsumer(eq("input"), anyString(), eq(processor.input()), Mockito.any()); + Mockito.verify(binder).bindProducer(eq("output"), eq(processor.output()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithBindingTargets.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithBindingTargets.java index 5e59a0639..a81314a21 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithBindingTargets.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithBindingTargets.java @@ -55,7 +55,7 @@ public class SinkBindingTestsWithBindingTargets { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - verify(binder).bindConsumer(eq("testtock"), anyString(), eq(testSink.input()), Mockito.any()); + verify(binder).bindConsumer(eq("testtock"), anyString(), eq(testSink.input()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithDefaults.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithDefaults.java index 285f795b5..d592c8b93 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithDefaults.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SinkBindingTestsWithDefaults.java @@ -54,7 +54,7 @@ public class SinkBindingTestsWithDefaults { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - verify(binder).bindConsumer(eq("input"), anyString(), eq(testSink.input()), Mockito.any()); + verify(binder).bindConsumer(eq("input"), anyString(), eq(testSink.input()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithBindingTargets.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithBindingTargets.java index 46b5735a8..49ab987bc 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithBindingTargets.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithBindingTargets.java @@ -62,8 +62,7 @@ public class SourceBindingTestsWithBindingTargets { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - verify(binder).bindProducer(eq("testtock"), eq(testSource.output()), Mockito.any()); - //Check error channel binding + verify(binder).bindProducer(eq("testtock"), eq(testSource.output()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithDefaults.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithDefaults.java index b59bbcb5a..6743985f8 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithDefaults.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/SourceBindingTestsWithDefaults.java @@ -20,8 +20,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.util.Properties; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -53,7 +51,7 @@ public class SourceBindingTestsWithDefaults { @SuppressWarnings("unchecked") @Test public void testSourceOutputChannelBound() { - verify(binder).bindProducer(eq("output"), eq(testSource.output()), Mockito.any()); + verify(binder).bindProducer(eq("output"), eq(testSource.output()), Mockito.any()); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1.java index 28cf61749..387bcc2fd 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1.java @@ -16,16 +16,16 @@ package org.springframework.cloud.stream.binder.stub1; -import java.util.Properties; - import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; /** * @author Marius Bogoevici * @author Mark Fisher */ -public class StubBinder1 implements Binder { +public class StubBinder1 implements Binder { private String name; @@ -38,12 +38,12 @@ public class StubBinder1 implements Binder { } @Override - public Binding bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) { + public Binding bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties properties) { return null; } @Override - public Binding bindProducer(String name, Object outboundBindTarget, Properties properties) { + public Binding bindProducer(String name, Object outboundBindTarget, ProducerProperties properties) { return null; } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1Configuration.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1Configuration.java index 9e432ef9d..eef040acd 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1Configuration.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub1/StubBinder1Configuration.java @@ -33,7 +33,7 @@ public class StubBinder1Configuration { @Bean @ConfigurationProperties("binder1") - public Binder binder() { + public Binder binder() { return new StubBinder1(); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2.java index 13186a3c8..98ac2e25b 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2.java @@ -16,16 +16,16 @@ package org.springframework.cloud.stream.binder.stub2; -import java.util.Properties; - import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; /** * @author Marius Bogoevici * @author Mark Fisher */ -public class StubBinder2 implements Binder { +public class StubBinder2 implements Binder { @SuppressWarnings("unused") private final StubBinder2Dependency stubBinder2Dependency; @@ -35,12 +35,12 @@ public class StubBinder2 implements Binder { } @Override - public Binding bindConsumer(String name, String group, Object inboundBindTarget, Properties properties) { + public Binding bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties properties) { return null; } @Override - public Binding bindProducer(String name, Object outboundBindTarget, Properties properties) { + public Binding bindProducer(String name, Object outboundBindTarget, ProducerProperties properties) { return null; } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2ConfigurationA.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2ConfigurationA.java index 40cf4f7e0..df6638200 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2ConfigurationA.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/stub2/StubBinder2ConfigurationA.java @@ -27,7 +27,7 @@ import org.springframework.context.annotation.Configuration; public class StubBinder2ConfigurationA { @Bean - public Binder binder(StubBinder2Dependency dependency) { + public Binder binder(StubBinder2Dependency dependency) { return new StubBinder2(dependency); } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java index 6da1fb1b2..1c8d1baed 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ChannelBindingServiceTests.java @@ -24,7 +24,9 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; import static org.mockito.Matchers.matches; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -49,7 +51,9 @@ import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderConfiguration; import org.springframework.cloud.stream.binder.BinderType; import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.DefaultBinderFactory; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.ChannelBindingServiceProperties; import org.springframework.cloud.stream.utils.MockBinderConfiguration; @@ -78,19 +82,19 @@ public class ChannelBindingServiceTests { new DefaultBinderFactory<>(Collections.singletonMap("mock", new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}), new Properties(), true))); - Binder binder = binderFactory.getBinder("mock"); + Binder binder = binderFactory.getBinder("mock"); ChannelBindingService service = new ChannelBindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); @SuppressWarnings("unchecked") Binding mockBinding = Mockito.mock(Binding.class); - when(binder.bindConsumer("foo", null, inputChannel, new Properties())) + when(binder.bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class))) .thenReturn(mockBinding); Collection> bindings = service.bindConsumer(inputChannel, inputChannelName); assertThat(bindings.size(), is(1)); Binding binding = bindings.iterator().next(); assertThat(binding, sameInstance(mockBinding)); service.unbindConsumers(inputChannelName); - verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName)); + verify(binder).bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)); verify(binding).unbind(); binderFactory.destroy(); } @@ -112,7 +116,7 @@ public class ChannelBindingServiceTests { new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}), new Properties(), true))); - Binder binder = binderFactory.getBinder("mock"); + Binder binder = binderFactory.getBinder("mock"); ChannelBindingService service = new ChannelBindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); @@ -121,9 +125,9 @@ public class ChannelBindingServiceTests { @SuppressWarnings("unchecked") Binding mockBinding2 = Mockito.mock(Binding.class); - when(binder.bindConsumer("foo", null, inputChannel, new Properties())) + when(binder.bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class))) .thenReturn(mockBinding1); - when(binder.bindConsumer("bar", null, inputChannel, new Properties())) + when(binder.bindConsumer(eq("bar"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class))) .thenReturn(mockBinding2); Collection> bindings = service.bindConsumer(inputChannel, "input"); @@ -138,8 +142,8 @@ public class ChannelBindingServiceTests { service.unbindConsumers("input"); - verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName)); - verify(binder).bindConsumer("bar", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName)); + verify(binder).bindConsumer(eq("foo"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)); + verify(binder).bindConsumer(eq("bar"), isNull(String.class), same(inputChannel), any(ConsumerProperties.class)); verify(binding1).unbind(); verify(binding2).unbind(); @@ -160,12 +164,12 @@ public class ChannelBindingServiceTests { new DefaultBinderFactory<>(Collections.singletonMap("mock", new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}), new Properties(), true))); - Binder binder = binderFactory.getBinder("mock"); + Binder binder = binderFactory.getBinder("mock"); ChannelBindingService service = new ChannelBindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); @SuppressWarnings("unchecked") Binding mockBinding = Mockito.mock(Binding.class); - when(binder.bindConsumer("foo", "fooGroup", inputChannel, new Properties())) + when(binder.bindConsumer(eq("foo"), eq("fooGroup"), same(inputChannel), any(ConsumerProperties.class))) .thenReturn(mockBinding); Collection> bindings = service.bindConsumer(inputChannel, inputChannelName); assertThat(bindings.size(), is(1)); @@ -173,7 +177,7 @@ public class ChannelBindingServiceTests { assertThat(binding, sameInstance(mockBinding)); service.unbindConsumers(inputChannelName); - verify(binder).bindConsumer("foo", props.getGroup(), inputChannel, properties.getConsumerProperties(inputChannelName)); + verify(binder).bindConsumer(eq("foo"), eq(props.getGroup()), same(inputChannel), any(ConsumerProperties.class)); verify(binding).unbind(); binderFactory.destroy(); } @@ -187,15 +191,14 @@ public class ChannelBindingServiceTests { new DefaultBinderFactory<>(Collections.singletonMap("mock", new BinderConfiguration(new BinderType("mock", new Class[]{MockBinderConfiguration.class}), new Properties(), true))); - Binder binder = binderFactory.getBinder("mock"); + Binder binder = binderFactory.getBinder("mock"); MessageChannel inputChannel = new DirectChannel(); @SuppressWarnings("unchecked") Binding mockBinding = Mockito.mock(Binding.class); @SuppressWarnings("unchecked") final AtomicReference dynamic = new AtomicReference<>(); - when(binder.bindProducer( - matches("bar"), any(DirectChannel.class), any(Properties.class))).thenReturn(mockBinding); + when(binder.bindProducer(matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(mockBinding); BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(binderFactory, properties, dynamicDestinationsBindable); ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); when(beanFactory.getBean("mock:bar", MessageChannel.class)) @@ -220,7 +223,7 @@ public class ChannelBindingServiceTests { resolver.setBeanFactory(beanFactory); MessageChannel resolved = resolver.resolveDestination("mock:bar"); assertThat(resolved, sameInstance(dynamic.get())); - verify(binder).bindProducer(eq("bar"), eq(dynamic.get()), any(Properties.class)); + verify(binder).bindProducer(eq("bar"), eq(dynamic.get()), any(ProducerProperties.class)); properties.setDynamicDestinations(new String[] { "mock:bar" }); resolved = resolver.resolveDestination("mock:bar"); assertThat(resolved, sameInstance(dynamic.get())); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/PropertiesClassResolutionTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/PropertiesClassResolutionTests.java new file mode 100644 index 000000000..9eef1a1e0 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/PropertiesClassResolutionTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binding; + +import org.junit.Assert; +import org.junit.Test; + +import org.springframework.cloud.stream.binder.AbstractBinder; +import org.springframework.cloud.stream.binder.Binder; +import org.springframework.cloud.stream.binder.Binding; +import org.springframework.cloud.stream.binder.ConsumerProperties; +import org.springframework.cloud.stream.binder.ProducerProperties; + + +/** + * @author Marius Bogoevici + */ +public class PropertiesClassResolutionTests { + + @Test + public void testDefaultResolution() { + SimpleBinderImplementation testBinder = new SimpleBinderImplementation(); + Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + @Test + public void testBinderImplementorWithCustomTypes() { + BinderImplementationWithCustomTypes testBinder = new BinderImplementationWithCustomTypes(); + Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + @Test + public void testExtendsAbstractBinderWithDefaultTypes() { + ExtendsAbstractBinderWithDefaultTypes testBinder = new ExtendsAbstractBinderWithDefaultTypes(); + Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + @Test + public void testExtendsAbstractBinderWithCustomTypes() { + ExtendsAbstractBinderWithCustomTypes testBinder = new ExtendsAbstractBinderWithCustomTypes(); + Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + @Test + public void testGenericBinderWithDefaultTypes() { + GenericBinderWithDefaultTypes testBinder = new GenericBinderWithDefaultTypes(); + Assert.assertEquals(ConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(ProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + @Test + public void testGenericBinderWithCustomTypes() { + GenericBinderWithCustomTypes testBinder = new GenericBinderWithCustomTypes(); + Assert.assertEquals(SubclassConsumerProperties.class, ChannelBindingService.resolveConsumerPropertiesType(testBinder)); + Assert.assertEquals(SubclassProducerProperties.class, ChannelBindingService.resolveProducerPropertiesType(testBinder)); + } + + private class SimpleBinderImplementation implements Binder { + + @Override + public Binding bindConsumer(String name, String group, Object inboundBindTarget, ConsumerProperties consumerProperties) { + return null; + } + + @Override + public Binding bindProducer(String name, Object outboundBindTarget, ProducerProperties producerProperties) { + return null; + } + } + + private class BinderImplementationWithCustomTypes implements Binder { + + @Override + public Binding bindConsumer(String name, String group, Object inboundBindTarget, SubclassConsumerProperties consumerProperties) { + return null; + } + + @Override + public Binding bindProducer(String name, Object outboundBindTarget, SubclassProducerProperties producerProperties) { + return null; + } + } + + private class ExtendsAbstractBinderWithDefaultTypes extends AbstractBinder { + + @Override + protected Binding doBindConsumer(String name, String group, Object inputTarget, ConsumerProperties properties) { + return null; + } + + @Override + protected Binding doBindProducer(String name, Object outboundBindTarget, ProducerProperties properties) { + return null; + } + } + + private class ExtendsAbstractBinderWithCustomTypes extends AbstractBinder { + + @Override + protected Binding doBindConsumer(String name, String group, Object inputTarget, SubclassConsumerProperties properties) { + return null; + } + + @Override + protected Binding doBindProducer(String name, Object outboundBindTarget, SubclassProducerProperties properties) { + return null; + } + } + + private class GenericBinderWithDefaultTypes extends AbstractBinder { + + @Override + protected Binding doBindConsumer(String name, String group, Object inputTarget, C properties) { + return null; + } + + @Override + protected Binding doBindProducer(String name, Object outboundBindTarget, P properties) { + return null; + } + } + + private class GenericBinderWithCustomTypes extends AbstractBinder { + + @Override + protected Binding doBindConsumer(String name, String group, Object inputTarget, C properties) { + return null; + } + + @Override + protected Binding doBindProducer(String name, Object outboundBindTarget, P properties) { + return null; + } + } + + private class SubclassConsumerProperties extends ConsumerProperties { + + } + + private class SubclassProducerProperties extends ProducerProperties { + + } +} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfigurationTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfigurationTests.java index a0985ea29..17cf5b20f 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfigurationTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/SpelExpressionConverterConfigurationTests.java @@ -16,23 +16,25 @@ package org.springframework.cloud.stream.config; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.expression.Expression; -import org.springframework.integration.config.EnableIntegration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** @@ -68,8 +70,9 @@ public class SpelExpressionConverterConfigurationTests { } @Configuration - @Import(SpelExpressionConverterConfiguration.class) - @EnableIntegration + @EnableBinding + @EnableAutoConfiguration + @Import(MockBinderRegistryConfiguration.class) @EnableConfigurationProperties(Pojo.class) public static class Config { diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java index a7a29aa9d..2ef5bc049 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedConsumerTest.java @@ -22,8 +22,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.util.Properties; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,7 +34,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.stream.annotation.Bindings; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; +import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration; import org.springframework.context.annotation.Import; @@ -61,10 +59,10 @@ public class PartitionedConsumerTest { @Test @SuppressWarnings("unchecked") public void testBindingPartitionedConsumer() { - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Properties.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ConsumerProperties.class); verify(binder).bindConsumer(eq("partIn"), anyString(), eq(testSink.input()), argumentCaptor.capture()); - Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.PARTITION_INDEX), equalTo("0")); - Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.COUNT), equalTo("2")); + Assert.assertThat(argumentCaptor.getValue().getInstanceIndex(), equalTo(0)); + Assert.assertThat(argumentCaptor.getValue().getInstanceCount(), equalTo(2)); verifyNoMoreInteractions(binder); } @@ -77,13 +75,10 @@ public class PartitionedConsumerTest { } - class PropertiesArgumentMatcher extends ArgumentMatcher { + class PropertiesArgumentMatcher extends ArgumentMatcher { @Override public boolean matches(Object argument) { - if (!(argument instanceof Properties)) { - return false; - } - return true; + return argument instanceof ConsumerProperties; } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java index b45ca8ef0..adfb5e06c 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/partitioning/PartitionedProducerTest.java @@ -21,8 +21,6 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import java.util.Properties; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +32,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.cloud.stream.annotation.Bindings; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.binder.Binder; -import org.springframework.cloud.stream.binder.BinderPropertyKeys; +import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.messaging.Source; import org.springframework.cloud.stream.utils.MockBinderRegistryConfiguration; import org.springframework.context.annotation.Import; @@ -59,10 +57,10 @@ public class PartitionedProducerTest { @Test @SuppressWarnings("unchecked") public void testBindingPartitionedProducer() { - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Properties.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ProducerProperties.class); verify(binder).bindProducer(eq("partOut"), eq(testSource.output()), argumentCaptor.capture()); - Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.NEXT_MODULE_COUNT), equalTo("3")); - Assert.assertThat(argumentCaptor.getValue().getProperty(BinderPropertyKeys.PARTITION_KEY_EXPRESSION), + Assert.assertThat(argumentCaptor.getValue().getPartitionCount(), equalTo(3)); + Assert.assertThat(argumentCaptor.getValue().getPartitionKeyExpression().getExpressionString(), equalTo("payload")); verifyNoMoreInteractions(binder); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderConfiguration.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderConfiguration.java index 40bf039c6..195bbf76a 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderConfiguration.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration; public class MockBinderConfiguration { @Bean - public Binder binder() { + public Binder binder() { return Mockito.mock(Binder.class, Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)); } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderRegistryConfiguration.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderRegistryConfiguration.java index 33cdde48d..8ff8f1b91 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderRegistryConfiguration.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/utils/MockBinderRegistryConfiguration.java @@ -41,7 +41,7 @@ public class MockBinderRegistryConfiguration { } @Bean - public Binder defaultBinder(BinderFactory binderFactory) { + public Binder defaultBinder(BinderFactory binderFactory) { return binderFactory.getBinder(null); } } diff --git a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/arbitrary-binding-test.properties b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/arbitrary-binding-test.properties index 3b26bca97..3f7db81c5 100644 --- a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/arbitrary-binding-test.properties +++ b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/arbitrary-binding-test.properties @@ -1,4 +1,4 @@ -spring.cloud.stream.bindings.foo=someQueue.0 -spring.cloud.stream.bindings.bar=someQueue.1 -spring.cloud.stream.bindings.baz=someQueue.2 -spring.cloud.stream.bindings.qux=someQueue.3 +spring.cloud.stream.bindings.foo.destination=someQueue.0 +spring.cloud.stream.bindings.bar.destination=someQueue.1 +spring.cloud.stream.bindings.baz.destination=someQueue.2 +spring.cloud.stream.bindings.qux.destination=someQueue.3 diff --git a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test-pubsub.properties b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test-pubsub.properties deleted file mode 100644 index cd9d0bc54..000000000 --- a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test-pubsub.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.cloud.stream.bindings.input=topic:testtock.0 -spring.cloud.stream.bindings.output=topic:testtock.1 diff --git a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test.properties b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test.properties index 5551f2a31..06b42431a 100644 --- a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test.properties +++ b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/processor-binding-test.properties @@ -1,2 +1,2 @@ -spring.cloud.stream.bindings.input=testtock.0 -spring.cloud.stream.bindings.output=testtock.1 +spring.cloud.stream.bindings.input.destination=testtock.0 +spring.cloud.stream.bindings.output.destination=testtock.1 diff --git a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties b/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties deleted file mode 100644 index 8781b67d8..000000000 --- a/spring-cloud-stream/src/test/resources/org/springframework/cloud/stream/binder/sink-binding-pubsub-test.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.cloud.stream.bindings.input.destination=topic:testpubsub -spring.cloud.stream.bindings.input.group=tgroup